IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Contribuez Discussion :

[Tuto] [OpenGL] Afficher un terrain avec un fichier RAW


Sujet :

Contribuez

  1. #1
    Rédacteur
    Avatar de Bakura
    Homme Profil pro
    Étudiant
    Inscrit en
    Septembre 2005
    Messages
    1 386
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Septembre 2005
    Messages : 1 386
    Points : 2 640
    Points
    2 640
    Par défaut [Tuto] [OpenGL] Afficher un terrain avec un fichier RAW
    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 :

    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]
    . 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.


    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 :

    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;
    }
    On créer un pointeur vers une variable de type FILE, qu'on alloue à NULL au départ.

    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.

    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];
    }
    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

    Voilà, maintenant, il ne reste plus qu'à dessiner tout ça !

    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 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.

    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.

    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);
    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
    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);
    Dessinons le premier triangle. C'est en fait très simple.

    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,...

  2. #2
    Rédacteur
    Avatar de Bakura
    Homme Profil pro
    Étudiant
    Inscrit en
    Septembre 2005
    Messages
    1 386
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Septembre 2005
    Messages : 1 386
    Points : 2 640
    Points
    2 640
    Par défaut
    Le tout texturé ^^ : Que c'est joli ^^... Voilà, vu que j'ai la chance de reprendre que lundi (c'est bien les retards au niveau des rénovations ^^).



    Bon demain faudra que je regarde pourquoi tout est à l'envers par rapport à ce que ça devrait être, améliorer le rendu du terrain (je sais pas pourquoi ça rend si mal), et ajouter la normale à chaque triangle... ^^. Ah... Après tout le temps que j'ai passé à essayer de comprendre ce foutu affichage de terrain, je suis content quand ça marche (presque) comme je veux ! (Et dire que c'étiat si facile,...)

Discussions similaires

  1. Afficher des cylindres avec OpenGL
    Par PetitProgJava dans le forum OpenGL
    Réponses: 3
    Dernier message: 10/01/2009, 02h14
  2. Réponses: 10
    Dernier message: 21/01/2007, 01h04
  3. Afficher un triangle avec OpenGL
    Par Premium dans le forum OpenGL
    Réponses: 19
    Dernier message: 19/10/2006, 09h06
  4. Afficher un tiangle avec OpenGL
    Par sidahmed dans le forum OpenGL
    Réponses: 5
    Dernier message: 18/03/2006, 21h50
  5. Afficher une image avec opengl
    Par kmaniche dans le forum OpenGL
    Réponses: 4
    Dernier message: 07/02/2006, 09h04

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo