En construisant les visualisations interactives de l’article précédent, une question s’est imposée : si Three.js peut rendre 225 bâtiments avec des ombres en temps réel sur un GPU grand public, pourquoi est-ce que Mappy Hour fait tout en CPU avec du code TypeScript ?
Ce qu’est la rasterisation
La rasterisation, c’est transformer un objet géométrique (un triangle, un polygone, une ligne) en pixels sur une grille régulière. Ton écran est une grille de pixels. Le GPU prend chaque triangle de la scène 3D, calcule quels pixels il couvre, et remplit ces pixels avec la bonne couleur.
C’est le principe fondamental du rendu 3D temps réel depuis les années 90. Pas besoin de suivre des rayons lumineux — on projette les triangles sur l’écran un par un, à une vitesse phénoménale. Un GPU moderne rasterise des milliards de triangles par seconde.
Le shadow mapping — la technique qu’utilise Three.js pour les ombres — est lui aussi de la rasterisation : on rend la scène une deuxième fois depuis le point de vue du soleil, et on stocke la profondeur de chaque pixel. Ensuite, pour chaque pixel de la vue normale, on vérifie s’il est “plus loin” que ce que le soleil voit. Si oui, il est dans l’ombre.
Où on l’utilise déjà (et on ne s’en rendait pas compte)
Mappy Hour utilise en fait de la rasterisation pour deux des trois couches de calcul :
Le terrain — SwissALTI3D est une grille raster à 2 mètres de résolution. Chaque “pixel” stocke l’altitude du sol nu. Quand on veut connaître l’altitude à un point donné, on fait un simple lookup dans la grille : grille[colonne][ligne]. C’est de la rasterisation au sens propre — une donnée continue (le relief) discrétisée sur une grille régulière.
La végétation — swissSURFACE3D est une grille raster à 0.5m. Chaque pixel stocke l’altitude de la surface (sol + arbres + tout ce qui dépasse). Pour tester si un arbre bloque le soleil, on lance un rayon dans la direction du soleil et on échantillonne la grille tous les 2 mètres. Si un échantillon est au-dessus de la ligne de visée, c’est bloqué. C’est du ray marching sur une grille raster — un hybride entre ray-tracing et rasterisation.
Le masque d’horizon — le DEM Copernicus à 30m est aussi une grille raster. On la parcourt jusqu’à 120 km dans chaque direction pour construire le profil d’obstruction.
La seule couche qui n’est pas raster, ce sont les bâtiments : des polygones 3D (footprints extrudés, mesh triangulés) testés par intersection rayon-triangle. Pourquoi ? Parce que les murs des bâtiments sont verticaux et discontinus. Un mur de 30 mètres de haut sur 50 centimètres d’épaisseur serait invisible dans une grille raster à 2m — il tomberait entre les pixels.
Pourquoi pas tout faire sur GPU alors ?
Three.js rend notre scène de 225 bâtiments à 60 fps. Mais ce qu’il fait est fondamentalement différent de ce que fait Mappy Hour :
| Three.js (GPU) | Mappy Hour (CPU) | |
|---|---|---|
| Objectif | “Ça a l’air juste” | “C’est juste” |
| Ombres | Shadow map (texture de résolution fixe) | Ray-tracing exact par bâtiment |
| Précision | ~1m (dépend de la shadow map) | Sub-métrique |
| Terrain | Pas de terrain au-delà de la scène | 120 km avec réfraction atmosphérique |
| Végétation | Non | Raster 0.5m le long du rayon |
| Résultat | Une image à regarder | Un booléen par point : soleil ou ombre |
Le GPU est optimisé pour rendre des images. Mappy Hour a besoin de répondre “oui” ou “non” pour des milliers de points à des centaines d’instants. Ce sont deux problèmes différents.
Les pistes GPU réalistes
WebGPU + Compute Shaders — Le successeur de WebGL permet d’exécuter du code arbitraire sur le GPU, pas seulement du rendu. On pourrait porter le ray-tracing des bâtiments sur GPU : charger la grille spatiale 64m et les footprints dans des buffers GPU, lancer un compute shader par point de la grille, et récupérer le résultat. Le gain potentiel est massif — des milliers de rays en parallèle au lieu de les traiter séquentiellement.
Mise à jour : cette piste a été implémentée — pas via WebGPU dans le navigateur, mais via Vulkan compute shaders côté serveur, en Rust. Le gain est réel : non seulement les bâtiments, mais aussi le terrain et la végétation tournent maintenant sur GPU en parallèle. Voir l’article dédié pour le pourquoi et le comment.
BVH (Bounding Volume Hierarchy) — Au lieu de la grille 64m plate, un arbre de volumes englobants permettrait un ray-tracing plus efficace sur GPU. C’est ce que font les moteurs de jeu pour le ray-tracing hardware (RT cores des GPU NVIDIA/AMD). Cette piste reste non explorée — le shadow map rend le BVH superflu pour les bâtiments.
Le vrai bottleneck — Avant de porter sur GPU, il faut regarder où le temps est passé. Les benchmarks de Mappy Hour montrent que le plus gros gain (45.8x) vient du partage de contexte par tuile, pas du ray-tracing lui-même. Optimiser la mauvaise chose ne sert à rien.
Les limites pour “tout Lausanne en 3D”
Si on voulait rendre toute la ville en 3D interactive dans le navigateur :
- 93’000 bâtiments = ~500k triangles (mesh détaillés). Un GPU moderne encaisse, mais il faut les charger. En JSON, c’est des centaines de mégaoctets. Il faudrait du glTF binaire ou des 3D Tiles (le format de Cesium/Google Earth).
- Le terrain sur 300 km² à 2m de résolution = 75 millions de points. Il faut du LOD (Level of Detail) — haute résolution près de la caméra, basse résolution au loin.
- La végétation à 0.5m sur la même zone = 1.2 milliard de pixels. Même problème, en pire.
C’est faisable. Mais c’est un projet d’une tout autre échelle que Mappy Hour — et surtout, ça ne répond pas à la question. Le rendu 3D interactif produit des images. Mappy Hour a besoin de booléens : soleil ou ombre, pour 62’500 points, 60 fois par jour.
Mise à jour — avril 2026 : le GPU shadow map est implémenté
Depuis l’écriture de cet article, le backend GPU shadow map a été implémenté. Pas via WebGPU ou Three.js, mais avec headless-gl — un contexte WebGL1 offscreen qui tourne en software (SwiftShader/ANGLE) côté serveur Node.js.
Le principe : charger les 93’000 bâtiments SwissBUILDINGS3D (907’000 triangles issus des vrais mesh DXF) dans un vertex buffer, rendre un shadow map 4096×4096 depuis le point de vue du soleil, et lire le depth buffer pour chaque point de la grille. Un render par instant solaire, puis un lookup O(1) par point.
Résultats :
| CPU ray-tracing | GPU shadow map | |
|---|---|---|
| Par évaluation | 2’000 µs | 22 µs |
| 19’968 évaluations | 40 s | 0.4 s |
| Speedup | — | 91x |
| Précision | référence | 93% match |
Le GPU ne tourne pas sur le GPU physique (Intel Arc) mais en software — le gain vient du pattern render-once/lookup-many, pas du hardware. Un seul rendu de shadow map remplace 19’968 intersections rayon-triangle individuelles.
Le plot twist : avec les bâtiments à 0.4 s, le goulot s’est déplacé. Le shadow map a résolu le ray-tracing des bâtiments (91x). Mais pour chaque point, il reste le terrain (horizon), la végétation (ray-march), et la combinaison finale — tout ça tourne encore en JavaScript, point par point. Le profiling après toutes les optimisations CPU (deep-dive) + shadow map donnait :
| Phase | Temps | Ce qui se passe |
|---|---|---|
| Shadow map GPU (bâtiments) | 0.4 s | 1 rendu + 62’500 lookups O(1) |
| Boucle JS (terrain + végétation + sunny) | ~12 s | 62’500 pts × 60 frames, séquentiel |
| Total par tuile | ~12 s | Le GPU fait 3%, le CPU fait 97% |
Le shadow map avait éliminé le goulot bâtiments. Le nouveau goulot, c’est la boucle JavaScript qui traite le terrain et la végétation pour chaque point. C’est exactement le problème que les compute shaders résolvent.
Conclusion
La rasterisation est omniprésente dans Mappy Hour — pour le terrain, la végétation, l’horizon, et maintenant aussi pour les bâtiments via un shadow map GPU. Le ray-tracing CPU exact reste disponible comme référence et fallback, mais le shadow map offre un compromis 91x plus rapide avec 93% de concordance.
Le prochain défi n’est plus les bâtiments — c’est le terrain. Le masque d’horizon qui raycaste à 120 km dans 360 directions est devenu le nouveau goulot. La piste : précalculer ces masques une fois pour toute la région au lieu de les recalculer à la volée.
Mise à jour : le terrain et la végétation sont maintenant aussi sur GPU, via des compute shaders Vulkan. Le shadow map headless-gl reste le chemin de production pour les bâtiments, mais un backend alternatif Rust/wgpu/Vulkan fait shadow map + terrain + végétation + masques sunny en un seul dispatch GPU. L’histoire complète : Du shadow map aux compute shaders Vulkan.