« Génération fractale de terrains » : différence entre les versions

De Wiki du LAMA (UMR 5127)
Aller à la navigation Aller à la recherche
 
(9 versions intermédiaires par le même utilisateur non affichées)
Ligne 1 : Ligne 1 :
Le but de ce projet était de générer des terrains 3D grâce à l'algorithme de [https://fr.wikipedia.org/wiki/Algorithme_Diamant-Carr%C3%A9 diamant-carré]. Le projet se découpe en 3 parties. Premièrement appliquer l'algorithme afin d'avoir une carte de hauteur. C'est une image en noir et blanc avec chaque pixel qui représente une certaine hauteur (noir étant la plus basse altitude et blanc étant la plus haute). Deuxièmement, créer une carte avec des couleurs à partir de la carte de hauteur. Troisièmement, créer un fichier [https://fr.wikipedia.org/wiki/Objet_3D_(format_de_fichier) .obj] (objets 3D) à partir de la carte de hauteur et appliquer sur cet objet 3D, la texture de la carte de couleur.
[page wiki en cours de rédaction]

Le but de ce projet était de générer des terrains 3D grâce à l'algorithme de [https://fr.wikipedia.org/wiki/Algorithme_Diamant-Carr%C3%A9 diamant-carré]. Le projet c'est découpé en 3 partie. Premièrement appliquer l'algorithme afin d'avoir un carte de hauteur. C'est une image en noir et blanc avec chaque pixel qui représente une certaine hauteur (noir étant la plus basse altitude et blanc étant la plus haute). Deuxièmement, créer un carte avec des couleurs à partir de la carte de hauteur. Troisièmement, créer un fichier [https://fr.wikipedia.org/wiki/Objet_3D_(format_de_fichier) .obj] (objets 3D) à partir de la carte de hauteur et appliquer sur cet objet 3D, la texture de la carte de couleur.




== Algorithmes utilisés ==
== Algorithmes utilisés ==
Le but de ce projet était d'utiliser l’algorithme de Diamant-carré. Or durant les test j'ai remarqué que la génération donnait lieu à des paysages côtier la plupart du temps. J'ai donc cherché un autre méthode de génération de carte de hauteur et j'ai rapidement trouvé le bruit de perlin.
J'ai commencé par utiliser l'algorithme de Diamant-carré. Or durant les tests j'ai remarqué que la génération donnait lieu à des paysages côtiers la plupart du temps. J'ai donc cherché une autre méthode de génération de carte de hauteur et j'ai rapidement trouvé le bruit de Perlin.
=== Diamant-Carré ===
=== Diamant-Carré ===
Le fonctionnement de cet algorithme est plutôt simple mais comporte une restriction de taille : il utilise une matrice carrée de taille <math>2^n + 1</math>. Il fonctionne ainsi :
Le fonctionnement de cet algorithme est plutôt simple mais comporte une restriction de taille : il utilise une matrice carrée de taille <math>2^n + 1</math>. Il fonctionne ainsi :
Ligne 11 : Ligne 9 :
- Initialise les quatre coins avec des valeurs aléatoires
- Initialise les quatre coins avec des valeurs aléatoires


- Phase diamant : le centre de chaque carré prend pour valeur la moyenne des 4 coins du carré et cette valeur est plus ou moins varié aléatoirement.
- Phase diamant : le centre de chaque carré prend pour valeur la moyenne des 4 coins du carré et cette valeur est plus ou moins variée aléatoirement.


- Phase carré : le centre de chaque diamant (losange) prend pour valeur la moyenne des 4 coins et cette valeur est plus ou moins varié aléatoirement.
- Phase carré : le centre de chaque diamant (losange) prend pour valeur la moyenne des 4 coins et cette valeur est plus ou moins variée aléatoirement.


- Le pas est divisé par deux et l'algorithme reprend à la phase diamant.
- Le pas est divisé par deux et l'algorithme reprend à la phase diamant.


Algorithme est décrit succinctement ici, pour plus de détails il existe une page Wikipédia [https://fr.wikipedia.org/wiki/Algorithme_Diamant-Carr%C3%A9 ici].
Le problème de cet algorithme est en deux parties : Premièrement, les paysages sont très souvent côtier. Deuxièmement les variations des nouveaux points ne sont pas réaliste. Si on regarde dans la nature, les variations d'altitudes sont forte dans les montages (pics etc...) et plus faible a moyenne et basse altitude (plaines , collines). Pour essayer de rendre la génération naturelle j'ai donc augmenter les variations en fonction de la hauteur du point. Pour ce qui est de la génération "côtière" une possibilité serait d'augmenter la grille et de fixer plus de point au départ. De plus, j'ai ajouté un paramètre appelé facteur de dénivelé qui permet de rendre la génération plus ou moins accidenté.
Le problème de cet algorithme est double : Premièrement, les paysages sont très souvent côtiers. Deuxièmement, les variations des nouveaux points ne sont pas réalistes. Si on regarde dans la nature, les variations d'altitudes sont fortes dans les montagnes (pics etc...) et plus faible à moyenne et basse altitude (plaines, collines). Pour essayer de rendre la génération naturelle j'ai donc augmenté les variations en fonction de la hauteur du point. Pour ce qui est de la génération "côtière" une possibilité serait d'augmenter la grille et de fixer la valeur de plus de points au départ. De plus, j'ai ajouté un paramètre appelé facteur de dénivelé qui permet de rendre la génération plus ou moins accidentée.


Voici quelques exemples de carte de hauteur grâce à l'algorithme de Diamant-carré. A gauche un facteur de dénivelé faible et à droite, un facteur de dénivelé plus élevé.
Voici quelques exemples de cartes de hauteur grâce à l'algorithme de Diamant-carré. A gauche un facteur de dénivelé faible et à droite, un facteur de dénivelé plus élevé.


[[Fichier:Image1.png]] [[Fichier:Image2.png]]
[[Fichier:Image1.png]] [[Fichier:Image2.png]]


=== Bruit de Perlin ===
=== Bruit de Perlin ===
Le bruit de perlin est une texture procédurale beaucoup utilisé pour ajouter du réalisme dans les générations de monde ou de texture en informatique. J'ai eu l'idée de l'utiliser en lisant l'article [https://medium.com/@yvanscher/playing-with-perlin-noise-generating-realistic-archipelagos-b59f004d8401 Playing with Perlin Noise: Generating Realistic Archipelagos]
Le bruit de Perlin est une texture procédurale beaucoup utilisée pour ajouter du réalisme dans les générations de mondes ou de textures en informatique. J'ai eu l'idée de l'utiliser en lisant l'article [https://medium.com/@yvanscher/playing-with-perlin-noise-generating-realistic-archipelagos-b59f004d8401 Playing with Perlin Noise: Generating Realistic Archipelagos]
Cet algorithme à beaucoup de paramètre qui permettent de grandement varier la génération et ce, de manière très précise. pour l'utiliser j'ai importé le module <code>noise</code> sur python.
Cet algorithme a beaucoup de paramètres qui permettent de grandement varier la génération et ce, de manière très précise. Pour l'utiliser, j'ai importé le module <code>noise</code> sur python.
J'ai ensuite utilisé les paramètres suivant pour généré mes cartes :
J'ai ensuite employé les paramètres suivants pour générer mes cartes :


- <code>shape</code> Un tuple de 2 entier qui sont les dimension de l'image
- <code>shape</code> Un tuple de 2 entiers qui représentent les dimensions de l'image.


- <code>scale</code> Un flottant qui représente l'échelle utilisée. plus il est élevé plus on "zoom" sur l'image.
- <code>scale</code> Un flottant qui représente l'échelle utilisée. Plus il est élevé, plus on "zoome" sur l'image.


- <code>octaves</code> Un entier qui représente le nombre de couche de détails. Par exemple l'octave 1 serait la forme globale de la montagne, le 2 serait les collines, le 3 serait les gros rochers sur cette montagne et le 4 serait les petits rochers. plus il y a d'octaves, plus la génération semble détaillée.
- <code>octaves</code> Un entier qui représente le nombre de couches de détails. Par exemple l'octave 1 serait la forme globale de la montagne, le 2 serait les collines, le 3 serait les gros rochers sur cette montagne et le 4 serait les petits rochers. Plus il y a d'octaves, plus la génération semble détaillée.


- <code>persistence</code> Un flottant, c'est l'impact que les octaves ont sur la forme générale.
- <code>persistence</code> Un flottant qui représente l'impact que les octaves ont sur la forme générale.


- <code>lacunarity</code> Un flottant, c'est la quantité de détails pris en compte pour les octaves
- <code>lacunarity</code> Un flottant qui représente la quantité de détails pris en compte pour les octaves.


- <code>seed</code> Un entier, c'est la graine de génération.
- <code>seed</code> Un entier qui représente la graine de génération.


- <code>facteur</code> Un flottant, c'est le facteur de dénivelé (un paramètre qui ne fait pas parti de noise, que j'ai ajouté moi même)
- <code>facteur</code> Un flottant qui représente le facteur de dénivelé (un paramètre qui ne fait pas partie de noise, que j'ai ajouté moi-même)


Comme des images valent mieux que des mots, prenons une carte généré grâce au paramètres suivants (le fonctionnement de hauteur_ocean sera abordé dans la partie couleur) :
Comme des images valent mieux que des mots, prenons une carte générée grâce aux paramètres suivants (le fonctionnement de hauteur_ocean sera abordé dans la partie couleur) :


<code>shape=(200,200),scale=50,octaves=5, persistence=0.5,lacunarity=2.0,seed=0,hauteur_ocean=0,facteur_denivele=0.10</code>
<code>shape=(200,200),scale=50,octaves=5, persistence=0.5,lacunarity=2.0,seed=0,hauteur_ocean=0,facteur_denivele=0.10</code>
Ligne 48 : Ligne 47 :
[[Fichier:base.png]]
[[Fichier:base.png]]


Sur l'image de gauche scale à diminué (31) sur celle de droite scale à augmenté (91)
Sur l'image de gauche, scale a diminué (31) sur celle de droite, scale a augmenté (91)


[[Fichier:base_scale1.png]] [[Fichier:base_scale2.png]]
[[Fichier:base_scale1.png]] [[Fichier:base_scale2.png]]


Sur l'image de gauche octave à diminué (2) sur celle de droite octave à augmenté (7)
Sur l'image de gauche, octave a diminué (2) sur celle de droite, octave a augmenté (7)


[[Fichier:base_octave1.png]] [[Fichier:base_octave2.png]]
[[Fichier:base_octave1.png]] [[Fichier:base_octave2.png]]


Sur l'image de gauche persistence à diminué (0.20) sur celle de droite persistence à augmenté (0.60)
Sur l'image de gauche, persistence a diminué (0.20) sur celle de droite, persistence a augmenté (0.60)


[[Fichier:base_persistence1.png]] [[Fichier:base_persistence2.png]]
[[Fichier:base_persistence1.png]] [[Fichier:base_persistence2.png]]


Sur l'image de gauche lacunarity à diminué (1.46) sur celle de droite lacunarity à augmenté (2.28)
Sur l'image de gauche, lacunarity a diminué (1.46) sur celle de droite, lacunarity a augmenté (2.28)


[[Fichier:base_lacunarity1.png]] [[Fichier:base_lacunarity2.png]]
[[Fichier:base_lacunarity1.png]] [[Fichier:base_lacunarity2.png]]


Sur l'image de gauche facteur à diminué (0.04) sur celle de droite facteur à augmenté (0.15)
Sur l'image de gauche, facteur a diminué (0.04) sur celle de droite, facteur a augmenté (0.15)


[[Fichier:base_facteur1.png]] [[Fichier:base_facteur2.png]]
[[Fichier:base_facteur1.png]] [[Fichier:base_facteur2.png]]
Ligne 73 : Ligne 72 :


== Couleurs ==
== Couleurs ==
Pour gérer les couleurs, j'ai commencé par une méthode très simple : choisir la couleur en fonction de l'altitude. Cependant cela donnait un effet cartoon mais manquait de détails. J'ai donc ensuite créer une nouvelle version en changeant pour de méthode pour les terres (l'océan reste cartoonesque). J'ai calculé la couleur en fonction de la "pente" du pixel. Cette pente est relative à ses 9 voisins. J'ai aussi ajouté un paramètre à la création de mes cartes pour varier la hauteur de l'océan. ce changement affecte uniquement les couleurs et non la carte de hauteur.
Pour gérer les couleurs, j'ai commencé par une méthode très simple : choisir la couleur en fonction de l'altitude. Cependant, cela donnait un effet cartoon mais manquait de détails. J'ai donc ensuite créé une nouvelle version en changeant de méthode pour les terres (l'océan reste cartoonesque). J'ai calculé la couleur en fonction de la "pente" du pixel. Cette pente est relative à ses 8 voisins. J'ai aussi ajouté un paramètre à la création de mes cartes pour varier la hauteur de l'océan. Ce changement affecte uniquement les couleurs et non la carte de hauteur.
voici donc quelques exemples avec les deux méthodes :
Voici donc quelques exemples avec les deux méthodes :
=== via l'altitude ===
=== via l'altitude ===
[[Fichier:Exemple_alt1.png]] [[Fichier:Exemple_alt2.png]] [[Fichier:Exemple_alt3.png]]
[[Fichier:Exemple_alt1.png]] [[Fichier:Exemple_alt2.png]]

[[Fichier:Exemple_alt3.png]]
=== via la pente ===
=== via la pente ===
[[Fichier:Exemple_pente.png]] [[Fichier:Exemple_pente2.png]] [[Fichier:Exemple_pente3.png]]
[[Fichier:Exemple_pente.png]] [[Fichier:Exemple_pente2.png]]

[[Fichier:Exemple_pente3.png]]


== Carte 3D ==
== Carte 3D ==
Ligne 87 : Ligne 90 :
- Les faces du maillage<br>
- Les faces du maillage<br>


Les coordonnées de points (v) en x et y sont les même que pour l'image mais en z ce sont les valeurs des pixels sur la carte de hauteur. ex : v x z y <br>
Les coordonnées de points (v) en x et y sont les mêmes que pour l'image mais en z ce sont les valeurs des pixels sur la carte de hauteur. ex : v x z y <br>
Les coordonnées de texture (vt) en x et y sont calculé ainsi : x_img/X et y_img/Y avec x_img et y_img les positions dans l'image et X et Y les dimensions de l'image. Il n'y à pas de coordonnées en z. ex : vt x y <br>
Les coordonnées de texture (vt) en x et y sont calculées ainsi : x_img/X et y_img/Y avec x_img et y_img les positions dans l'image et X et Y les dimensions de l'image. Il n'y a pas de coordonnées en z. ex : vt x y <br>
Les faces du maillage (f) sont représentés par un ensemble de points formant un carré ABCD. ex : f A/A B/B C/C D/D <br>
Les faces du maillage (f) sont représentées par un ensemble de points formant un carré ABCD. ex : f A/A B/B C/C D/D <br>
'''Comment visualiser les fichier OBJ sur blender ?'''<br>
'''Comment visualiser les fichiers OBJ sur blender ?'''<br>
- Lancer Blender et créer un nouveau projet (général)<br>
- Lancer Blender et créer un nouveau projet (général)<br>
- Supprimer le cube d'origine (cliquer dessus et presser la touche x)<br>
- Supprimer le cube d'origine (cliquer dessus et presser la touche x)<br>
- importer un fichier le fichier obj et faire un copier glisser de carte_couleur<br>
- importer le fichier obj et faire un copier-glisser de carte_couleur<br>
[[Fichier:aide_blender1.png]]<br>
[[Fichier:aide_blender1.png]]<br>
-Sélectionnez l'objet map (en haut à droite) et n'hésitez pas à dézoomer pour voir la carte 3D.<br>
- Sélectionner l'objet map (en haut à droite) et ne pas hésiter à dézoomer pour voir la carte 3D.<br>
- Allez dans l’onglet material properties (sphère avec des triangles rouges)<br>
- Aller dans l’onglet material properties (sphère avec des triangles rouges)<br>
Puis sélectionnez les même paramètres que sur l'image suivante (les points rouges indiquent ce qu'il faut changer).<br>
Puis sélectionner les mêmes paramètres que sur l'image suivante (les points rouges indiquent ce qu'il faut changer).<br>
Vous pouvez choisir le visue que vous préférez entre closest et cubic.<br>
Vous pouvez choisir le visuel que vous préférez entre "closest" et "cubic".<br>
[[Fichier:aide_blender2.png]]<br>
[[Fichier:aide_blender2.png]]<br>
Puis en haut à gauche sélectionnez Viewport Shading pour voir la texture.<br>
Puis en haut à droite sélectionner Viewport Shading pour voir la texture.<br>
[[Fichier:aide_blender3.png]]<br>
[[Fichier:aide_blender3.png]]<br>


=== Diamant-Carré ===
=== Diamant-Carré ===
exemple sans shaders (ciel et lumière) :<br>
[[Fichier:Exemple_3d_map1.png]]<br>
[[Fichier:Exemple_3d_map1.png]]<br>
exemple sans ciel ni lumière<br>
exemple avec shaders :<br>
[[Fichier:Exemple_3d_map2.png]]<br>
[[Fichier:Exemple_3d_map2.png]]<br>

exemple avec shaders<br>


=== Bruit de Perlin ===
=== Bruit de Perlin ===
exemple sans shaders (ciel et lumière) :<br>
[[Fichier:Exemple_3d_map3.png]]<br>
[[Fichier:Exemple_3d_map3.png]]<br>
exemple sans ciel ni lumière. <br>
exemple avec shaders :<br>
[[Fichier:Exemple_3d_map4.png]]<br>
[[Fichier:Exemple_3d_map4.png]]<br>
exemple avec shaders. <br>


== Rivières ==
== Rivières ==
C'est en lisant [http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/ cet article] que j'ai eu l'idée d'ajouter des rivière à la génération. Or contrairement à cet article, ma carte n'est pas faite de polygone différents les uns des autres. J'ai donc pensé à un autre algorithme qui fonctionne ainsi :<br>
C'est en lisant [http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/ cet article] que j'ai eu l'idée d'ajouter des rivières à la génération. Or contrairement à cet article, ma carte n'est pas faite de polygones différents les uns des autres. J'ai donc pensé à un autre algorithme qui fonctionne ainsi :<br>
1- Parcourt chaque pixel de la carte, chaque pixel à une certaine probabilité de devenir un début de rivière.<br>
1- L'algorithme parcourt chaque pixel de la carte, chaque pixel a une certaine probabilité de devenir un début de rivière.<br>
2- Si une rivière est commencée, alors l'algorithme regarde tout les voisins du pixel sur lequel le pointeur est (au début le pointeur est sur le pixel "source" qui est bleu).<br>
2- Si une rivière est commencée, alors l'algorithme regarde tous les voisins du pixel sur lequel le pointeur est positionné (au début le pointeur est sur le pixel "source" qui est bleu).<br>
3- L'algorithme choisi ensuite le pixel le plus bas, place le pointeur dessus et le colore en bleu.<br>
3- L'algorithme choisit ensuite le pixel le plus bas, place le pointeur dessus et le colore en bleu.<br>
4- L’algorithme continue ainsi jusqu’à ce qu'il arrive à la hauteur de l'océan ou qu'il n'y ait pas de pixel plus bas .<br>
4- L’algorithme continue ainsi jusqu’à ce qu'il arrive à la hauteur de l'océan ou qu'il n'y ait pas de pixel plus bas.<br>
5- La rivière est tracée en entier, l'algorithme continue de parcourir les pixels de la carte.<br>
5- La rivière est tracée en entier, l'algorithme continue de parcourir les pixels de la carte.<br>


Or cet algorithme peut être amélioré notamment en simulant une érosion si aucun pixel plus bas n'est trouvé parmi les voisins. Dans ce cas, on prend le plus bas des pixels possibles (sauf celui du pointeur) et on diminue sa hauteur de 1 et on reprend à l'étape 2.
Or cet algorithme peut être amélioré notamment en simulant une érosion si aucun pixel plus bas n'est trouvé parmi les voisins. Dans ce cas, on prend le plus bas des pixels possibles (sauf celui du pointeur) et on diminue sa hauteur de 1 et on reprend à l'étape 2.


Je me suis alors heurté à un bug que je n'ai pas encore réussi à fixer. Certaines rivière s'arrêtent toutes seules en plein milieu d'une pente sans aucune raison apparente. Sur l'exemple qui suit, la première image est une vision global et la suivante est une vision plus proche. Nous pouvons constater que la rivière devrait suivre la flèche bleu or ce n'est pas le cas.<br>
Je me suis alors heurté à un bug que je n'ai pas encore réussi à fixer. Certaines rivières s'arrêtent toutes seules en plein milieu d'une pente sans aucune raison apparente. Sur l'exemple qui suit, la première image est une vision globale et la suivante est une vision plus proche. Nous pouvons constater que la rivière devrait suivre la flèche bleue, or ce n'est pas le cas.<br>
[[Fichier:Exemple_bug1.png|left|vignette|redresse=2.3|]] [[Fichier:Exemple_bug2.png|redresse=2|]] <br>
[[Fichier:Exemple_bug1.png|left|vignette|redresse=2.3|]] [[Fichier:Exemple_bug2.png|redresse=2|]] <br>


Si ce bug est fixé, mes objectifs seront les suivants :<br>
Si ce bug est fixé, mes objectifs seront les suivants :<br>
- Lorsque une rivière B croise une rivière A, la rivière B continue à coter de la A de manière à donner une rivière plus large.<br>
- Lorsqu'une rivière B croise une rivière A, la rivière B continue à côté de la A de manière à donner une rivière plus large.<br>
- Faire en sorte de remplir des bassins et créer des lacs.<br>
- Faire en sorte de remplir des bassins et créer des lacs.<br>


== Extensions et Code ==
== Code ==
J'ai réalisé ce projet sous python 3.8 avec les modules suivants : <code>random</code>, <code>PIL</code>, <code>noise</code>, <code>numpy</code> et <code>tkinter</code>.<br>

Le code est entièrement disponible sur GitHub [https://github.com/Hearstgo/VISI201_G-n-ration_terrains ici].
== Sources & inspiration ==
Voici l'interface graphique du générateur : <br>
[http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/ Polygonal Map Generation for Games]
[[Fichier:interface.png]]


Et voici comment est structuré le projet :<br>
[http://mewo2.com/notes/terrain/ Generating fantasy maps]
Les flèches noires symbolisent l'importation de fonctions et les vertes la création de fichier<br>
map_3D.py n'est pas utile à l'interface mais j'ai développé les fonctions de map_3D dans le but de générer un fichier obj et sa texture sans interface.
[[Fichier:projet_structure.png]]


== Sources & inspirations ==
[https://azgaar.github.io/Fantasy-Map-Generator/ Azgaar, un générateur de carte]
[http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/ Polygonal Map Generation for Games]<br>
[http://mewo2.com/notes/terrain/ Generating fantasy maps]<br>
[https://azgaar.github.io/Fantasy-Map-Generator/ Azgaar, un générateur de carte]<br>
[https://www.youtube.com/watch?v=HMbWj_KX-4k Un rapide tutoriel blender pour ajouter un ciel]<br>

Dernière version du 11 mai 2020 à 12:06

Le but de ce projet était de générer des terrains 3D grâce à l'algorithme de diamant-carré. Le projet se découpe en 3 parties. Premièrement appliquer l'algorithme afin d'avoir une carte de hauteur. C'est une image en noir et blanc avec chaque pixel qui représente une certaine hauteur (noir étant la plus basse altitude et blanc étant la plus haute). Deuxièmement, créer une carte avec des couleurs à partir de la carte de hauteur. Troisièmement, créer un fichier .obj (objets 3D) à partir de la carte de hauteur et appliquer sur cet objet 3D, la texture de la carte de couleur.


Algorithmes utilisés

J'ai commencé par utiliser l'algorithme de Diamant-carré. Or durant les tests j'ai remarqué que la génération donnait lieu à des paysages côtiers la plupart du temps. J'ai donc cherché une autre méthode de génération de carte de hauteur et j'ai rapidement trouvé le bruit de Perlin.

Diamant-Carré

Le fonctionnement de cet algorithme est plutôt simple mais comporte une restriction de taille : il utilise une matrice carrée de taille . Il fonctionne ainsi :

- Initialise les quatre coins avec des valeurs aléatoires

- Phase diamant : le centre de chaque carré prend pour valeur la moyenne des 4 coins du carré et cette valeur est plus ou moins variée aléatoirement.

- Phase carré : le centre de chaque diamant (losange) prend pour valeur la moyenne des 4 coins et cette valeur est plus ou moins variée aléatoirement.

- Le pas est divisé par deux et l'algorithme reprend à la phase diamant.

Algorithme est décrit succinctement ici, pour plus de détails il existe une page Wikipédia ici. Le problème de cet algorithme est double : Premièrement, les paysages sont très souvent côtiers. Deuxièmement, les variations des nouveaux points ne sont pas réalistes. Si on regarde dans la nature, les variations d'altitudes sont fortes dans les montagnes (pics etc...) et plus faible à moyenne et basse altitude (plaines, collines). Pour essayer de rendre la génération naturelle j'ai donc augmenté les variations en fonction de la hauteur du point. Pour ce qui est de la génération "côtière" une possibilité serait d'augmenter la grille et de fixer la valeur de plus de points au départ. De plus, j'ai ajouté un paramètre appelé facteur de dénivelé qui permet de rendre la génération plus ou moins accidentée.

Voici quelques exemples de cartes de hauteur grâce à l'algorithme de Diamant-carré. A gauche un facteur de dénivelé faible et à droite, un facteur de dénivelé plus élevé.

Image1.png Image2.png

Bruit de Perlin

Le bruit de Perlin est une texture procédurale beaucoup utilisée pour ajouter du réalisme dans les générations de mondes ou de textures en informatique. J'ai eu l'idée de l'utiliser en lisant l'article Playing with Perlin Noise: Generating Realistic Archipelagos Cet algorithme a beaucoup de paramètres qui permettent de grandement varier la génération et ce, de manière très précise. Pour l'utiliser, j'ai importé le module noise sur python. J'ai ensuite employé les paramètres suivants pour générer mes cartes :

- shape Un tuple de 2 entiers qui représentent les dimensions de l'image.

- scale Un flottant qui représente l'échelle utilisée. Plus il est élevé, plus on "zoome" sur l'image.

- octaves Un entier qui représente le nombre de couches de détails. Par exemple l'octave 1 serait la forme globale de la montagne, le 2 serait les collines, le 3 serait les gros rochers sur cette montagne et le 4 serait les petits rochers. Plus il y a d'octaves, plus la génération semble détaillée.

- persistence Un flottant qui représente l'impact que les octaves ont sur la forme générale.

- lacunarity Un flottant qui représente la quantité de détails pris en compte pour les octaves.

- seed Un entier qui représente la graine de génération.

- facteur Un flottant qui représente le facteur de dénivelé (un paramètre qui ne fait pas partie de noise, que j'ai ajouté moi-même)

Comme des images valent mieux que des mots, prenons une carte générée grâce aux paramètres suivants (le fonctionnement de hauteur_ocean sera abordé dans la partie couleur) :

shape=(200,200),scale=50,octaves=5, persistence=0.5,lacunarity=2.0,seed=0,hauteur_ocean=0,facteur_denivele=0.10

Base.png

Sur l'image de gauche, scale a diminué (31) sur celle de droite, scale a augmenté (91)

Base scale1.png Base scale2.png

Sur l'image de gauche, octave a diminué (2) sur celle de droite, octave a augmenté (7)

Base octave1.png Base octave2.png

Sur l'image de gauche, persistence a diminué (0.20) sur celle de droite, persistence a augmenté (0.60)

Base persistence1.png Base persistence2.png

Sur l'image de gauche, lacunarity a diminué (1.46) sur celle de droite, lacunarity a augmenté (2.28)

Base lacunarity1.png Base lacunarity2.png

Sur l'image de gauche, facteur a diminué (0.04) sur celle de droite, facteur a augmenté (0.15)

Base facteur1.png Base facteur2.png

Et enfin, si on change la seed à 2 voici le résultat :

Base seed1.png

Couleurs

Pour gérer les couleurs, j'ai commencé par une méthode très simple : choisir la couleur en fonction de l'altitude. Cependant, cela donnait un effet cartoon mais manquait de détails. J'ai donc ensuite créé une nouvelle version en changeant de méthode pour les terres (l'océan reste cartoonesque). J'ai calculé la couleur en fonction de la "pente" du pixel. Cette pente est relative à ses 8 voisins. J'ai aussi ajouté un paramètre à la création de mes cartes pour varier la hauteur de l'océan. Ce changement affecte uniquement les couleurs et non la carte de hauteur. Voici donc quelques exemples avec les deux méthodes :

via l'altitude

Exemple alt1.png Exemple alt2.png

Exemple alt3.png

via la pente

Exemple pente.png Exemple pente2.png

Exemple pente3.png

Carte 3D

Pour la gestion des cartes en 3D j'ai utilisé le format OBJ. Pour les cartes, j'ai découpé le fichier en 3 parties:

- Les coordonnées de points.
- Les coordonnées de texture (entre 0 et 1)
- Les faces du maillage

Les coordonnées de points (v) en x et y sont les mêmes que pour l'image mais en z ce sont les valeurs des pixels sur la carte de hauteur. ex : v x z y
Les coordonnées de texture (vt) en x et y sont calculées ainsi : x_img/X et y_img/Y avec x_img et y_img les positions dans l'image et X et Y les dimensions de l'image. Il n'y a pas de coordonnées en z. ex : vt x y
Les faces du maillage (f) sont représentées par un ensemble de points formant un carré ABCD. ex : f A/A B/B C/C D/D
Comment visualiser les fichiers OBJ sur blender ?
- Lancer Blender et créer un nouveau projet (général)
- Supprimer le cube d'origine (cliquer dessus et presser la touche x)
- importer le fichier obj et faire un copier-glisser de carte_couleur
Aide blender1.png
- Sélectionner l'objet map (en haut à droite) et ne pas hésiter à dézoomer pour voir la carte 3D.
- Aller dans l’onglet material properties (sphère avec des triangles rouges)
Puis sélectionner les mêmes paramètres que sur l'image suivante (les points rouges indiquent ce qu'il faut changer).
Vous pouvez choisir le visuel que vous préférez entre "closest" et "cubic".
Aide blender2.png
Puis en haut à droite sélectionner Viewport Shading pour voir la texture.
Aide blender3.png

Diamant-Carré

exemple sans shaders (ciel et lumière) :
Exemple 3d map1.png
exemple avec shaders :
Exemple 3d map2.png


Bruit de Perlin

exemple sans shaders (ciel et lumière) :
Exemple 3d map3.png
exemple avec shaders :
Exemple 3d map4.png

Rivières

C'est en lisant cet article que j'ai eu l'idée d'ajouter des rivières à la génération. Or contrairement à cet article, ma carte n'est pas faite de polygones différents les uns des autres. J'ai donc pensé à un autre algorithme qui fonctionne ainsi :
1- L'algorithme parcourt chaque pixel de la carte, chaque pixel a une certaine probabilité de devenir un début de rivière.
2- Si une rivière est commencée, alors l'algorithme regarde tous les voisins du pixel sur lequel le pointeur est positionné (au début le pointeur est sur le pixel "source" qui est bleu).
3- L'algorithme choisit ensuite le pixel le plus bas, place le pointeur dessus et le colore en bleu.
4- L’algorithme continue ainsi jusqu’à ce qu'il arrive à la hauteur de l'océan ou qu'il n'y ait pas de pixel plus bas.
5- La rivière est tracée en entier, l'algorithme continue de parcourir les pixels de la carte.

Or cet algorithme peut être amélioré notamment en simulant une érosion si aucun pixel plus bas n'est trouvé parmi les voisins. Dans ce cas, on prend le plus bas des pixels possibles (sauf celui du pointeur) et on diminue sa hauteur de 1 et on reprend à l'étape 2.

Je me suis alors heurté à un bug que je n'ai pas encore réussi à fixer. Certaines rivières s'arrêtent toutes seules en plein milieu d'une pente sans aucune raison apparente. Sur l'exemple qui suit, la première image est une vision globale et la suivante est une vision plus proche. Nous pouvons constater que la rivière devrait suivre la flèche bleue, or ce n'est pas le cas.

Exemple bug1.png

Exemple bug2.png

Si ce bug est fixé, mes objectifs seront les suivants :
- Lorsqu'une rivière B croise une rivière A, la rivière B continue à côté de la A de manière à donner une rivière plus large.
- Faire en sorte de remplir des bassins et créer des lacs.

Code

J'ai réalisé ce projet sous python 3.8 avec les modules suivants : random, PIL, noise, numpy et tkinter.
Le code est entièrement disponible sur GitHub ici. Voici l'interface graphique du générateur :
Interface.png

Et voici comment est structuré le projet :
Les flèches noires symbolisent l'importation de fonctions et les vertes la création de fichier
map_3D.py n'est pas utile à l'interface mais j'ai développé les fonctions de map_3D dans le but de générer un fichier obj et sa texture sans interface. Projet structure.png

Sources & inspirations

Polygonal Map Generation for Games
Generating fantasy maps
Azgaar, un générateur de carte
Un rapide tutoriel blender pour ajouter un ciel