← Retour aux articles

La Génération Procédurale de Terrains de Jeu : Entre Mathématiques et Art Numérique

Introduction

La création d'environnements 3D massifs pour les jeux modernes représente l'un des défis les plus complexes de l'industrie du divertissement numérique. Des titres comme Fortnite et PUBG proposent des îles entières explorant des kilomètres carrés, peuplées de structures naturelles et artificielles cohérentes. Comment ces univers gigantesques sont-ils construits sans que chaque rocher soit placé manuellement par un artiste ?

La réponse réside dans la génération procédurale de terrain, une approche mathématique qui crée des paysages complexes et naturels en utilisant des algorithmes plutôt que du travail manuel. Plutôt que de peindre chaque grain de sable, les développeurs définissent des règles—des formules—qui s'appliquent partout, générant une diversité infinie à partir de paramètres simples.

Cet article explore les fondements mathématiques et techniques derrière cette magie numérique, du Domain Warping aux simulations d'érosion hydraulique, en passant par les stratégies d'intégration d'infrastructures routières.

Pourquoi les Terrains Procéduraux ?

Avant d'explorer la complexité des algorithmes, il faut comprendre pourquoi cette approche a d'abord existé, et pourquoi elle reste incontournable.

Les Limites du Travail Manuel

Créer une île de 2 kilomètres carrés en détail photéaliste demande des centaines de jours-homme. Chaque vallée, chaque crête, chaque pente doit être sculptée, texturée, et optimisée pour les performances. Si un créatif décide de modifier la topologie, des semaines de travail peuvent devenir inutiles.

La génération procédurale change cette équation : modifiez une valeur (appelée "seed"), et l'île se régénère en quelques secondes. Cette approche n'élimine pas le travail artistique, elle le démultiplie.

Cohérence Géologique et Jouabilité

Les terrains générés manuellement risquent d'être incohérents. Pourquoi une vallée remonte-t-elle sans raison apparente ? Pourquoi les routes ne suivent-elles pas la gravité ? Les algorithmes procéduraux, fondés sur des principes géophysiques, produisent des paysages qui "semblent" naturels parce qu'ils respectent les lois qui façonnent les vrais paysages.

Pour la jouabilité, c'est critique : les joueurs doivent instantanément comprendre le terrain du regard. Une crête évidente, des vallées lisibles, des fleuves qui coulent logiquement—tout cela émerge naturellement d'une bonne implémentation procédurale.

Le Fondement : Le Bruit et la Génération de Forme

Avant les algorithmes avancés, il y a le bruit—la graine de toute complexité.

Du Bruit Classique aux Limites de la Simplicité

La méthode la plus simple pour générer un terrain consiste à superposer plusieurs couches de bruit de Perlin ou de bruit de Simplex, une technique appelée fractional Brownian motion (fBm). Chaque couche ajoute des détails de plus en plus fins.


hauteur(x, y) = Σ Amplitude_i × Bruit(x × Fréquence_i, y × Fréquence_i)

Cette approche fonctionne pour les nuages ou les collines érodées. Mais elle crée un problème majeur : l'isotropie artificielle. Le terrain ressemble à du coton. Il manque de structure, de caractère. Pire encore, il souffre d'une propriété mathématique appelée homoscédasticité : la rugosité (la complexité) est identique partout. Une vallée et un pic rocheux ont le même niveau de détail local, ce qui est géologiquement faux.

Domain Warping : Tordre l'Espace Lui-Même

La solution, formalisée par Inigo Quilez et popularisée dans les studios AAA, est le Domain Warping. Au lieu d'additionner simplement les bruits, on déforme les coordonnées elles-mêmes avant d'évaluer le bruit.

Imaginez un artiste qui crée un paysage sur une feuille de caoutchouc. Maintenant, étirez et pliez cette feuille aléatoirement. Les montagnes qui étaient droites deviennent sinueuses, les vallées s'allongent. C'est le Domain Warping.

Mathématiquement :


position_déformée = position_originale + bruit_distortion × amplitude
hauteur = bruit(position_déformée)

L'effet est spectaculaire : les structures deviennent anisotropes (étirées dans certaines directions), les crêtes se tordent comme des chaînes montagneuses réelles façonnées par la tectonique des plaques. Le terrain passe du "synthétique" au "naturel" en une seule étape conceptuelle.

Implémentation Pratique dans Blender

Dans Blender, grâce aux Geometry Nodes, un système nodal pour manipuler la géométrie, cette opération s'exécute en temps quasi-réel :


1. Charger la position du sommet (vertex)
2. Évaluer un bruit Perlin à cette position → obtenir un vecteur de déplacement
3. Ajouter ce vecteur à la position originale
4. Évaluer un DEUXIÈME bruit à la nouvelle position
5. Utiliser ce résultat pour déplacer le sommet en hauteur (Z)

En Python (via l'API Blender bpy), cela devient automatisable :

python
def build_domain_warping_graph(node_tree, iterations=2, amplitude=50.0):
nodes = node_tree.nodes
links = node_tree.links

# Point d'entrée
pos_node = nodes.new("GeometryNodeInputPosition")
current_vector = pos_node.outputs["Position"]

for i in range(iterations):
# Créer un bruit de perturbation
noise = nodes.new("ShaderNodeTexNoise")
noise.inputs[0].default_value = 0.02 * (i + 1)

# Centrer le bruit entre -0.5 et 0.5
sub = nodes.new("ShaderNodeVectorMath")
sub.operation = 'SUBTRACT'
sub.inputs[1].default_value = (0.5, 0.5, 0.5)

# Mettre à l'échelle la force
scale = nodes.new("ShaderNodeVectorMath")
scale.operation = 'SCALE'
scale.inputs[2].default_value = amplitude / (i + 1)

# Ajouter au vecteur actuel
add = nodes.new("ShaderNodeVectorMath")
add.operation = 'ADD'

# Lier tous les éléments
links.new(current_vector, noise.inputs["Vector"])
links.new(noise.outputs["Color"], sub.inputs[0])
links.new(sub.outputs["Vector"], scale.inputs[0])
links.new(current_vector, add.inputs[0])
links.new(scale.outputs["Vector"], add.inputs[1])

current_vector = add.outputs["Vector"]

return current_vector

Ce pattern se répète pour chaque "couche" d'algorithme avancé. La programmation procédurale dans Blender n'est pas visuelle seule—c'est de la programmation déclarative, où chaque nœud est une instruction, et les liens sont des flux de données.

Les Biomes : Structurer l'Île en Régions Distinctes

Une île de Battle Royale ne peut pas être un chaos monolithe. Elle doit proposer une variété tactique : des forêts pour se cacher, des montagnes pour observer, des plaines pour se déplacer rapidement. Ces zones sont les biomes.

Segmentation Voronoi

La méthode standard pour partitionner l'espace est le diagramme de Voronoi. Imaginez une île vue du ciel. Vous placez N "germes" aléatoires. Chaque point de l'île appartient au germe le plus proche. Les résultats ? Des régions polygonales naturelles.


Pour chaque point (x, y) :
biome = find_nearest_seed(x, y)

Mais les frontières Voronoi brutes sont trop nettes, trop géométriques. Les transitions entre la forêt et la montagne devraient être progressives, avec des zones de transition.

Transitions Lisses avec Blur Attribute

La solution dans Blender : au lieu d'une transition binaire (tu es dans le biome A ou le biome B), on utilise une moyenne pondérée des hauteurs basée sur la proximité à la frontière. Le nœud Blur Attribute effectue une moyenne gaussienne des valeurs des sommets voisins.


1. Assigner une hauteur de base à chaque biome (forêt : 10m, plateau : 50m)
2. Utiliser la métrique de distance au bord Voronoi pour créer un masque
3. Flou ce masque via Blur Attribute
4. Interpoler entre les hauteurs via ce masque lissé

Le résultat ? Des transitions organiques où les terrains se fondent les uns dans les autres, sans falaise abrupte. C'est une technique d'interpolation sophistiquée, mais exécutée de façon massive et parallélisée sur tous les sommets simultanément.

L'Érosion Hydraulique : Donner une Histoire au Terrain

À ce stade, le terrain a une forme, des biomes distincts. Mais il manque quelque chose : une histoire géologique. Il n'a pas d'érosion. Les montagnes devraient avoir des vallées creusées par des fleuves, des dépôts alluvionnaires dans les creux, des éboulis le long des pentes.

Simuler cela en physics réaliste est coûteux. Mais grâce aux Simulation Zones de Blender, on peut implémenter une approximation efficace : le modèle des Tuyaux Virtuels (Virtual Pipes).

Les Équations de Saint-Venant Simplifiées

Ce modèle simule l'écoulement d'eau sur une grille discrète. Chaque cellule (x, y) contient :

- b : la hauteur du sol (bedrock)
- d : la profondeur d'eau au-dessus
- s : les sédiments en suspension
- f : les flux sortants vers les 4 voisins (gauche, droite, haut, bas)

À chaque pas temporel (frame) :

1. Précipitations : de l'eau arrive
2. Calcul des flux : l'eau s'écoule vers les cellules voisines selon la pente
3. Mise à jour du niveau d'eau : les cellules se remplissent ou se vident
4. Transport sédimentaire : l'eau érode le sol si elle se déplace vite, dépose des sédiments si elle ralentit

Mathématiquement, le flux vers un voisin dépend de la différence d'altitude totale (sol + eau) :


Δh = (b + d)_self - (b + d)_neighbor
flux = max(0, flux_précédent + Δt × g × Δh / distance)

La gravité g accélère l'écoulement. Les cellules hautes poussent l'eau vers les cellules basses. L'eau emporte des sédiments selon sa vitesse et la pente locale :


capacité_sédiments = K × sin(pente) × |vitesse|

Si sédiments < capacité : l'eau érode
Si sédiments > capacité : l'eau dépose

Résultat Visuel

Après 100 à 500 frames de simulation, le terrain n'est plus lisse. Les vallées convergent naturellement, créant des lits de rivière. Les crêtes restent dentelées. Les plaines s'accumulent des sédiments. C'est un terrain qui raconte une histoire hydrogéologique.

Des Montagnes Réalistes : Le Ridged Multifractal Noise

Pour les environnements alpins spécifiques (Alpes, Rocheuses), le simple Domain Warping ne suffit pas. Il faut un algo encore plus sophistiqué : le Ridged Multifractal Noise.

Le Problème : L'Homoscédasticité Revisitée

Les terrains générés par fBm souffrent d'un défaut persistant : la rugosité locale est uniforme partout. Un pic rocheux et une vallée alluviale ont le même niveau de détail microscopique. Géologiquement, c'est faux. Une vallée devrait être lisse (accumulation de sédiments), un pic devrait être ébréché (érosion glaciaire intense).

La Solution : Cascade Multiplicative

Au lieu d'additionner simplement les bruits de chaque fréquence, le Ridged Multifractal les mélange multiplicativement. Chaque octave (couche de fréquence) "observe" la couche précédente pour décider de son amplitude.


Pour chaque octave i :
signal = (offset - |bruit()|)² # Transformation "ridged"
poids = signal × gain_de_l_octave_précédente
poids = clamp(poids, 0, 1) # Limiter la croissance exponentielle

résultat += signal × poids × gain^i

Décomposons cela :

- (offset - |bruit|)² : cette transformation crée des crêtes aigues. Au lieu de vagues lisses, on obtient des pics acérés. C'est l'inversion de la valeur absolue qui fait la magie.

- signal × poids : si la forme macroscopique (octaves précédentes) a créé une vallée (signal bas), cette octave contribue peu (poids bas). Si elle a créé un pic (signal haut), cette octave contribue beaucoup (poids haut). Résultat : les vallées restent lisses, les pics restent dentelés.

- gain^i : avec chaque octave supplémentaire, l'amplitude diminue exponentiellement, assurant la convergence.

Calibration pour un Style Alpin

Pour obtenir l'esthétique des Alpes (par opposition aux Rocheuses ou à l'Himalaya), il faut régler précisément :

- Lacunarité (saut de fréquence) : entre 2.1 et 2.2. Une valeur autour de 2.0 produit un aspect trop régulier. Au-delà de 2.2, c'est trop chaotique.

- Ridge Offset (décalage de crête) : entre 0.85 et 0.95. Des valeurs basses créent des "arêtes de couteau" typiques des vallées glaciaires. Des valeurs hautes produisent des sommets plus arrondis.

- Warp Strength : la force du Domain Warping. Une valeur > 0.5 tord les crêtes sur elles-mêmes, formant des "cirques" (amphithéâtres glaciaires).

Avec ces paramètres bien calibrés, le même algorithme peut générer l'Himalaya chaotique, les Alpes élégantes ou le Massif Central érodé. C'est la beauté de la paramétrisation.

Intégration des Routes : Projection et Lissage Topologique

Une île de Battle Royale n'est pas qu'un paysage. Elle contient des infrastructures : des routes pour les véhicules, des bâtiments, des passages stratégiques.

Les routes posent un défi unique : elles doivent être parfaitement lisses (les voitures ne peuvent pas rouler sur du terrain dentelé) tout en s'intégrant au paysage chaotique de l'île. Une simple projection verticale des coordonnées de route sur la hauteur du terrain crée un chaos—la route hérite de toutes les irrégularités.

Le Repère de Frenet et la Projection Intelligente

Mathématiquement, une route est une courbe paramétrée (généralement une courbe de Bézier). Pour chaque point du terrain, on trouve le point le plus proche sur la courbe. Le vecteur tangent à la courbe indique la direction de la route. Les vecteurs normal et binormal définissent le plan transversal.

Pour lisser le terrain le long de la route :

1. Calculer la distance d de chaque sommet à la courbe
2. Si d < largeur_route, la hauteur doit être celle de la courbe lissée (pas le terrain brut)
3. Si d > largeur_route + zone_transition, la hauteur reste naturelle
4. Entre les deux, interpoler progressivement

La clé est l'interpolation lissée (utilisant smoothstep) plutôt que linéaire. Cela crée des talus naturels plutôt que des falaises abruptes.

python
for chaque_sommet in terrain :
distance_to_road = closest_point_on_curve(sommet)
interpolation_factor = smoothstep(road_width + blend_width, road_width, distance)

hauteur_route = hauteur_curve[closest_point]
hauteur_naturelle = hauteur_terrain[sommet]

sommet.z = mix(hauteur_naturelle, hauteur_route, interpolation_factor)

Pour éviter le Z-fighting (clignotement visuel entre la route et le terrain), il faut aussi exécuter et joindre les géométries correctement, supprimant les faces du terrain sous la route et créant un maillage de route distinct que l'on fusionne.

Automatisation en Python : Du Concept au Produit

Tout ce que nous avons décrit jusqu'ici doit être automatisé. Construire manuellement un graphe de nœuds avec centaines de nœuds reliés est impensable pour une itération rapide.

C'est là qu'entre en jeu l'API Python de Blender (bpy).

Paradigme de Programmation Déclarative

Dans Blender, au lieu de cliquer sur une interface, on code :

python
import bpy

class TerrainPipeline:
def __init__(self, name="BattleRoyale_Island"):
self.name = name
self.setup_scene()
self.node_tree = self.create_node_tree()

def setup_scene(self):
# Créer l'objet mesh vierge
mesh = bpy.data.meshes.new(self.name)
self.obj = bpy.data.objects.new(self.name, mesh)
bpy.context.collection.objects.link(self.obj)

# Ajouter le modificateur Geometry Nodes
mod = self.obj.modifiers.new(name="GeoNodes", type='NODES')
self.mod = mod

def create_node_tree(self):
# Créer un nouveau graphe de nœuds
tree = bpy.data.node_groups.new(
name=f"{self.name}_NodeTree",
type='GeometryNodeTree'
)
self.mod.node_group = tree
return tree

def add_node(self, type_name, label="", location=(0,0)):
node = self.node_tree.nodes.new(type_name)
node.label = label
node.location = location
return node

def build_full_pipeline(self):
# 1. Créer la grille de base (2000m × 2000m, 1024² sommets)
grid = self.add_node(
"GeometryNodeMeshGrid",
label="Base Grid",
location=(0, 0)
)
grid.inputs["Size X"].default_value = 2000.0
grid.inputs["Size Y"].default_value = 2000.0
grid.inputs["Vertices X"].default_value = 1024
grid.inputs["Vertices Y"].default_value = 1024

# 2. Appliquer Domain Warping (section 2)
warped_geom = self.apply_domain_warping(grid)

# 3. Appliquer les biomes Voronoi (section 3)
biomed_geom = self.apply_voronoi_biomes(warped_geom)

# 4. Appliquer l'érosion hydraulique (section 4)
eroded_geom = self.apply_hydraulic_erosion(biomed_geom)

# 5. Projeter les routes (section 5)
final_geom = self.apply_road_projection(eroded_geom)

# Connecter à la sortie
output = self.node_tree.nodes.new("GeometryNodeGroupOutput")
self.node_tree.links.new(final_geom, output.inputs["Geometry"])

pipeline = TerrainPipeline("MyIsland")
pipeline.build_full_pipeline()

Gestion des Simulation Zones

Les Simulation Zones, nécessaires pour l'érosion hydraulique, sont particulièrement délicates à créer via Python. Elles requièrent une paire de nœuds (Input/Output) liés par un ID de zone interne.

python
def create_simulation_zone(self):
# L'opérateur bpy crée automatiquement la paire
bpy.ops.node.add_simulation_zone()

# Trouver les nœuds créés
sim_input = None
sim_output = None

for node in self.node_tree.nodes:
if node.type == 'GEOMETRY_NODE_SIMULATION_INPUT':
sim_input = node
elif node.type == 'GEOMETRY_NODE_SIMULATION_OUTPUT':
sim_output = node

# Créer les sockets d'état (eau, sédiments, flux, etc.)
if sim_input:
sim_input.outputs.new('NodeSocketFloat', 'water_depth')
sim_input.outputs.new('NodeSocketFloat', 'sediment')
# ... autres attributs ...

La difficulté réside dans la synchronisation entre Input et Output, et dans la gestion des noms d'attributs. Un typo dans un nom peut rompre le pipeline entier.

Performance et Optimisation

Une grille de 1024 × 1024 sommets représente 1 million de calculs par frame. À 60 fps, c'est 60 millions d'opérations par seconde. C'est faisable en GPU (Blender supporte CUDA/Metal), mais pour la simulation d'érosion hydraulique, chaque frame dépend du précédent, ce qui limite le parallélisme.

Strategies Pratiques

Baking : Les simulations d'érosion se pré-calculent une fois (frames 0 à 500), puis le résultat est gelé pour le jeu final. Pas besoin de recalculer à chaque frame.

Réduction de Résolution : Pour la simulation, travailler sur une grille 512 × 512, puis upsampler le résultat final via interpolation. Cela réduit les calculs d'un facteur 4.

GPU Compute Shaders : Pour aller au-delà, Blender peut appeler des compute shaders externes (GLSL, Metal) qui tournent sur le GPU natif, bien plus rapide que les Geometry Nodes.

Cas d'Usage Réels : Fortnite et No Man's Sky

Fortnite

Fortnite génère ses îles via une combinaison de :
- World Machine (logiciel externe) pour les heightmaps de base
- Houdini pour la distribution des biomes et des structures
- Custom C++ pour les affectations dynamiques (loot, spawn points)

Chaque saison, une nouvelle île peut être générée et itérée rapidement.

No Man's Sky

No Man's Sky va plus loin : chaque planète (18+ quintillions) est générée procéduralement, y compris la topographie, la flore, la faune, les bâtiments aliens. Ils utilisent un système appelé UberNoise, une cascade sophistiquée de Ridged Multifractal avec des dérivées analytiques pour mapper la végétation directement en fonction de la pente.

Le résultat ? Des univers pratiquement infinis, entièrement stockables en code (une graine = une planète).

Limitations et Futurs Développements

Limitations Actuelles

Réalisme Géological : Même avec érosion hydraulique, les terrains générés ne reproduisent pas fidèlement les complexités réelles (failles géologiques, épidéposition minérale, action tectonique).

Cohérence à Large Échelle : Génerer une île cohérente de 20 km² à la résolution nécessaire pour un jeu exige des compromis.

Temps de Génération : Une simulation d'érosion de 500 frames sur une grille 4K prend des heures, même sur GPU.

Perspectives Futures

Machine Learning : Entraîner des réseaux de neurones à partir de terrains réels (via LiDAR) pourrait produire des terrains plus fidèles.

Simulation en Temps Réel GPU : Des compute shaders dédiés pourraient simuler l'érosion en parallèle massif, réduisant les temps à quelques secondes.

Intégration Prédictive : Plutôt que de générer puis corriger, anticiper les routes, les bâtiments, la végétation dans la boucle de génération elle-même.

Conclusion

La génération procédurale de terrain est un art et une science qui combine mathématiques avancées, physique computationnelle, et pragmatisme d'ingénierie. Des concepts comme le Domain Warping, le Ridged Multifractal et les simulations d'érosion hydraulique transforment quelques paramètres en univers détaillés et jouables.

Grâce à des outils comme Blender et son API Python, ces techniques, autrefois réservées aux studios AAA avec des équipes d'ingénieurs dédiées, deviennent accessibles à des développeurs indépendants. Une île entière peut naître de centaines de lignes de code Python, itérée en secondes, et ajustée selon le gameplay souhaité.

Qu'il s'agisse de concevoir le prochain Battle Royale ou de créer des mondes infinis pour une exploration spatiale virtuelle, la génération procédurale restera au cœur de la création d'expériences immersives à grande échelle. Et comme les algorithmes continuent d'évoluer, les environnements virtuels ne feront que devenir plus riches, plus authentiques, et plus étonnants.