Bonjour à tous,
Voilà, c'est la rentrée pour moi dans quelques heures, mais avant je vous soumets un petit code source. C'est sur comment afficher un terrain à l'aide d'un fichier RAW. Il y a évidemment plusieurs tutoriaux sur le site déjà, mais je les trouve un peu difficile à aborder, et surtout il ne traite trop de comment afficher un terrain. J'ai donc décidé de faire une petite source et de l'expliquer ^^. Celle ci ne gère rien, ni l'affichage de texture, ni l'éclairage, ni de variables exotiques pour faire varier tout un tas de paramètres... Ici, on se contente juste d'afficher la map ^^... Pour la source (utilise la SDL pour l'affichage), je me suis très largement inspiré d'un tuto trouvé sur Internet, mais j'ai simplifié l'affichage du terrain et utiliser des Triangle_strip au lieu de Triangles
Si il y a des erreurs, merci de me les signaler ^^ (le code est pas très propre, notamment avec les noms des variables avec des variables globales avec des m_... Je sais que c'est que pour les variables membres, je changerai si j'ai le temps ce soir...
Voici le résultat qu'on obtiendra à la fin (bien évidemment, les heightmap étant généré aléatoirement, vous n'obtiendrez pas le même terrain ^^) :
Tout d'abord pour générer le terrain nous allons utiliser le logiciel Terragen, téléchargeable ici : http://www.planetside.co.uk/terragen/
Je ne sais aps très bien me servir du logiciel, mais on fera avec ^^... Ouvrez Terragen, Dans la boite Landscape cliquez sur Generate Terrain... Choississez l'une des méthodes (allez, par exemple Perlin Noise). On va faire un terrain pas trop accidenté, donc baissez un peu la valeur de glaciation, puis cliquez sur Generate Terrain. Quittez cette fenetre et revenez sur la boite Landscape. Cliquez sur Size, et, si ce n'est pas déjà fait, cliquez sur 257*257... Alors premier truc bizarre, si des gens plus expérimentés avec ce logiciel pourrait m'aider : Pour afficher correctement le heightmap, il faut que je spécifie exactement cette taille, soit 257*257 (ou les autres tailles de Terragen, 129*129, 513*513,...), c'est quoi ces tailles bizarres ? Pourquoi ne pourrait-on pas spécifier 256*256, 128*128,... ? Ou les tailles que l'on veut (300*300 ?)...
Enfin bref, continuons. Vous avez maintenant votre map, reste plus qu'à l'enregistrer. Toujours dans Landscape, cliquez sur Export. Choisissez le format Raw 8 bits (pas les autres RAW !). On aura donc, comme écrit juste en dessous, chaque pixel prenant une place 1 byte, ca nous fait donc 257*257 bytes, soit un fichier à 66049 bytes. Enregistrez le fichier RAW dans le repertoire de votre projet, et quittez Terragen...
Nous allons en premier lieu charger le fichier RAW. Donc dans ce format, chaque pixel est représenté par une valeur entre 0 et 255, ce qui nous arrange évidemment... La valeur 0 étant représenté par le noir sur le heightmap et le 255 par le blanc. Il y a évidemment plusieurs dégradés de gris pour définir différentes hauteurs.
Nous allons en premier lieu définir quelques variables globales (pour faciliter le code, sinon vous pouvez faire comme j'ai fait sur mon plus gros programme, créer une classe Terrain). Les voici :
const GLuint TAILLE_MAP = 257; // La largeur et la longueur de la map
const GLint m_iPrecision = 4; // Précision de la map
GLubyte m_ubHeightmap [TAILLE_MAP * TAILLE_MAP]; // Tableau contenant les données du Heightmap
La première est assez explicite je pense... La map étant un carré, la taille de la map est donc un largeur et la longueur, qui est de 257 comme nous l'avons spécifié dans Terragen.
La seconde elle, concerne la précision de la map. Plus cette valeur est faible (1 la minimum), plus le terrain sera jolie mais plus cela prendre en performance. J'ai mis 4. En fait, imaginez qu'avec une valeur de 1, chaque pixel du fichier RAW seront dessiner. Avec une valeur de 4, on dessinera donc, le pixel [0][0], le pixel [0] [4], le pixel [0] [8],... Ce sera moins précis mais aussi moins gourmand.
Enfin la troisième variable est un tableau de taille TAILLE_MAP * TAILLE_MAP (257 * 257 = 66049 éléments). Il s'agit d'un tableau à 1 seule dimension, à 2 dimensions cela aurait été plus simple, mais tous les tutos que j'ai vu préfère utiliser à une dimension, pour des raisons de performance... Faisons avec donc ! Je vais recopier les termes du tuto de Games Creator dont l'adresse est plus haut :
. Le premier élément d'un tableau à double dimensions étant par convention les lignes, et le deuxième les colonnes, il suffit donc de multiplier la ligne par la longueur (ou largeur ici) + l'élémen t de la longueur, comme écrit dans l'exemple.Ce schéma nous montre bien qu'un tableau tab[7][7] peut être représenté sous la forme tab[7*7], dans ce cas, l'élément tab[4][5] sera l'élément tab[4*7+5] donc tab[33]
Nous avons donc notre variable ou nous pouvons stocker tous les éléments... Nous allons donc à présent charger le fichier RAW et écrire tout ceci dans notre variable m_ubHeightmap...
Pour ceci on va créer une fonction ChargerFichierRAW :
On créer un pointeur vers une variable de type FILE, qu'on alloue à NULL au départ.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 bool ChargerFichierRAW (const char * nom, int taille) { FILE * pFile = NULL; // On crée un pointeur vers nouveau fichier pFile = fopen (nom, "rb"); // On ouvre le fichier .RAW if (!pFile) { std::cerr << "Erreur dans l'ouverture du fichier."; return false; } fread (&m_ubHeightmap, taille, 1, pFile); // On copie dans le tableau un nombre nTaille de 1 byte // à partir du fichier pFile fclose (pFile); // On n'a plus besoin de ce fichier, on le ferme return true; }
Puis on ouvre le fichier grâce à la fonction fopen... Le "rb" signifie qu'on ouvre en mode binaire. On vérifie que l'allocation s'est bien passée. Si oui, on copie ensuite les données du fichier pFile dans notre tableau !
Pour ceci on utilise la fonction fread avec en premier argument un pointeur vers la variable qui va accueillir les données (donc notre tableau), le deuxième argument, le nombre d'éléments (donc TAILLE_MAP * TAILLE_MAP), le troisième la taille d'un élément (donc 1 byte), et enfin un pointeur vers la variable contenant les données à copier.
On se retrouve donc avec notre tableau qui contient pour chaque élément du tableau une valeur comprise entre 0 et 255... Cette valeur qui sera donc LA HAUTEUR, la valeur y donc (et accessoirement la couleur).
lorsque nous allons définir les vertex dans la méthode de dessin, il va nous falloir retrouver cette hauteur y.
Nous allons donc définir une nouvelle fonction pour retrouver cette valeur.
Tout est expliqué ! On passe en argument à la fonction la valeur x (la largeur) et z (la longueur, ou profondeur), et on cherche la hauteur correspondante ! Je pense que les commentaires sont assez explicites
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 GLfloat LireHauteur (GLuint x, GLuint z) { if (x >= TAILLE_MAP) x = TAILLE_MAP - 1; // On vérifie qu'on ne dépasse pas les bornes du tableau if (z >= TAILLE_MAP) z = TAILLE_MAP - 1; // On vérifie qu'on ne dépasse pas les bornes du tableau // RAPPEL // On accède avec comme calcul z * largeur + x. Ainsi pour trouver l'élément // tab [9][4], soit par convention la ligne 9 et colonne 4, cela donne : // 4 * m_iLargeur (par exemple 15) : 4 * 15 + 9 = 69. return (GLfloat)m_ubHeightmap [z * TAILLE_MAP + x]; }![]()
Voilà, maintenant, il ne reste plus qu'à dessiner tout ça !
Nous allons donc appeler deux boucles for. La première se chargera d'incrémenter la largeur (x), et l'autre la profondeur (z). A chaque iteration, on incrémente les valeurs de m_iPrecision.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 void DessinerTerrain () { for (unsigned int x = 0 ; x < TAILLE_MAP - m_iPrecision ; x += m_iPrecision) { glBegin (GL_TRIANGLE_STRIP); for (unsigned int z = 0 ; z < TAILLE_MAP - m_iPrecision ; z += m_iPrecision) { // Couleur pour le premier triangle GLubyte color = (GLubyte) LireHauteur (x, z); glColor3ub (color, color, color); // Premier triangle glVertex3f (x, (LireHauteur (x, z) / 10.0f), z); glVertex3f (x, (LireHauteur (x, z + m_iPrecision) / 10.0f), z + m_iPrecision); glVertex3f (x + m_iPrecision, (LireHauteur (x + m_iPrecision, z) / 10.0f), z); // Couleur pour le deuxième triangle GLubyte colorSuivante = (GLubyte) LireHauteur (x + m_iPrecision, z + m_iPrecision); glColor3ub (colorSuivante, colorSuivante, colorSuivante); // Deuxième triangle glVertex3f (x + m_iPrecision, (LireHauteur (x + m_iPrecision, z + m_iPrecision) / 10.0f), z + m_iPrecision); } glEnd (); } }
Nous avons tout ce qui nous faut, il nous reste plus qu'à dessiner. Nous allons utiliser des TRIANGLE_STRIP. Les QUADS étant déconseillé un peu partout, et les TRIANGLES obligeant à dessiner deux vertices de plus à chaque fois, le mieux est donc des TRIANGLE_STRIP.
On récupère en premier lieu la couleur du premier triangle. On part du principe que plus la hauteur est élevé, plus la couleur sera blanche. Inversement plus la hauteur sera basse, plus la couleur sera noire. Il suffit donc logiquement de récupérer la valeur y (qui est comprise je le rappelle entre 0 et 255), puis de définir la couleur avec un appel à glColor.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 // Couleur pour le premier triangle GLubyte color = (GLubyte) LireHauteur (x, z); glColor3ub (color, color, color);
Dessinons le premier triangle. C'est en fait très simple.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 // Premier triangle glVertex3f (x, (LireHauteur (x, z) / 10.0f), z); glVertex3f (x, (LireHauteur (x, z + m_iPrecision) / 10.0f), z + m_iPrecision); glVertex3f (x + m_iPrecision, (LireHauteur (x + m_iPrecision, z) / 10.0f), z);
Lorsqu'on dessine un triangle simplement, on écrit (si on considère qu'on le dessine dans le sens inverse des aiguilles d'une montre) :
glVertex3f (x, y, z);
glVertex3f (x, y, z + 1);
glVertex3f (x + 1, y, z);
Puis pour faire un carré dans le cas d'un triangle strip, la quatrième vertice serait :
glVertex3f (x + 1, y, z + 1);
On va donc faire pareil ici, sauf qu'on va remplacer les + 1 par + m_iPrecision et les y par LireHauteur (x, z); avec x et z les mêmes que les x et z que dans l'appel à glVertex. Ainsi si l'appel a glVertex3f est (x + m_iPrecision), l'appel à LireHauteur se fera avec le même x, soit LireHauteur (x + m_iPrecision, z).
Si vous ne comprenez pas trop, essayez sur une feuille de dessin, c'est très efficace. Vous avez juste sûrement remarqué qu'on divise le résultat du LireHauteur par 10. En effet imaginez que la hauteur soit dans le heightmap un blanc parfait, ça va donc nous renvoyez une hauteur de 255... Ce qui serait vraiment immense et nous donnerait que des piques plutôt qu'un terrain.
On divise donc apr 10. Si vous voulez un terrain encore plus plat, divisez par une valeur supérieur, sinon par une valeur inférieur, mais 10 ça semble bien ^^...
Voilà maintenant le zip contenant le programme : http://membres.lycos.fr/hl2connection/TestSDL.zip
Il y a plusieurs améliorations possibles, notamment ne pas dessiner les faces du dessous de la map...
Allez, maintenant je file en cours pour la rentrée ^^.
EDIT : concernant gluLookAt j'ai vraiment un gros problème avec cette fonction,... J'ai l'impression que ce que je fais avec glTranslatef (et c'est pas qu'une impression), bne les résultats ne sont pas les mêmes à gluLookAt, alors que j'avais lu que gluLookAt regroupait glTranslate et glRotate,...
En fait si vous compilez mon programme et que vous vous mettez un peu en hauteur, vous remarquerez que la map est, par rapport au heightmap, à l'envers... Il faut que je passe "en dessous" pour la voir droite,.. ce qui me laisse un peu abassourdi
EDIT 2 : Je viens de bien reregarder l'image que j'ai mis plus haut, et je pense qu'il y a une petite erreur au niveau des couleurs ce qui semble avoir pour effet d'avoir des triangles très visibles,...
Partager