Un moteur de rendu par lancer de rayons (ray tracing) sur CPU, écrit en Rust, sans bibliothèque graphique externe. Seule la crate rayon est utilisée pour le parallélisme.
Scène vitrine en 1920x1080 : 8 objets, réflexions, anti-aliasing 4x4.
- Ce que fait le programme
- Les maths en jeu
- Lancer le programme
- Architecture
- Choix de conception
- Mesures de performances
- Profilage
- Limites et pistes
- Licence
- Formes : sphères, plans infinis.
- Éclairage : une source ponctuelle, modèle diffus de Lambert et lumière ambiante.
- Ombres portées : ombres dures via un rayon d'ombre vers la lumière.
- Réflexions récursives : coefficient de 0 (mat) à 1 (miroir), reflet teinté par la couleur de l'objet, profondeur maximale de 5 rebonds.
- Anti-aliasing : suréchantillonnage 4x4.
- Couleur & sortie : correction gamma (γ = 2), écriture directe au format binaire PPM (P6).
- Parallélisme : distribution des calculs de pixels avec
rayon.
Un rayon part de l'origine O dans la direction unitaire d :
Intersection avec une sphère de centre C et de rayon R (avec oc = O − C) : une équation du second degré en t, dont on garde la plus petite racine positive.
Éclairage diffus de Lambert, avec n la normale et l la direction vers la lumière :
Direction d'un rayon réfléchi :
- Rust (testé avec Rust 1.98.1,
edition = "2024").
Le binaire génère directement le fichier image.ppm à la racine :
cargo run --releasePour visualiser ou convertir l'image générée :
ffmpeg -i image.ppm docs/rendu.png| Module | Rôle |
|---|---|
vec3 |
Vecteur 3D, opérateurs (+, -, *, neg, +=), dot, normalized, reflechi, Sum |
ray |
Rayon : origine, direction, point à la distance t |
forme |
enum Forme { Sphere, Plan } : équation d'intersection et normale |
objet |
Objet { forme, couleur, reflexion }, recherche de l'obstacle le plus proche (plus_proche) |
couleur |
Alias Couleur = Vec3, constantes, conversion en octets [u8; 3] avec gamma |
lumiere |
Source de lumière ponctuelle : Lumiere { position } |
scene |
Scene { objets, lumiere }, scènes prédéfinies demo et vitrine |
rendu |
Caméra, couleur du ciel, calcul d'éclairage récursif, ombres, anti-aliasing |
main |
Dimensions, lancement du rendu, écriture du fichier PPM |
enumplutôt queBox<dyn Trait>pour les formes : environ 5 fois plus rapide. L'absence d'appel indirect (pas de vtable), une disposition mémoire contiguë et l'inlining par le compilateur expliquent cet écart.- Matériau dans
Objet, pas dans chaqueForme: séparer la géométrie de l'aspect visuel a permis d'ajouter la réflexion en une seule ligne dansObjet, sans toucher aux calculs d'intersection. - Calculs en
f64: la conversion en octetsu8se fait uniquement à l'écriture finale du fichier, évitant les pertes de précision pendant les rebonds. - Rendu séparé de l'écriture : le moteur remplit un Vec en mémoire avant d'écrire le fichier sur le disque. C'est nécessaire pour pouvoir utiliser
into_par_iter()de la craterayon, qui permet de paralléliser le calcul. - Grille régulière plutôt qu'aléatoire pour l'anti-aliasing.
Image de 400 x 225, mode release, médiane de 3 lancements, sur une machine à 2 vCPU (Debian 13, Rust 1.98.1) :
| Étape | Calcul | Écriture | Remarques |
|---|---|---|---|
| 1. Sphères unies | 4,0 ms | 14,0 ms (P3) | L'écriture texte (PPM ASCII). |
| 2. Éclairage | 4,5 ms | ~14 ms | Coût minime du produit scalaire de Lambert. |
| 3. Ombres portées | 6,0 ms | ~14 ms | +32 % seulement : any() s'arrête dès le 1er obstacle rencontré, et le ciel ne lance aucun rayon d'ombre. |
| 4. Réflexions | ~9 ms | ~11-15 ms | Passer d'une profondeur max de 1 à 50 ne change presque rien : peu d'objets se reflètent mutuellement. |
| 5. Anti-aliasing 4x4 | ~172 ms | ~15 ms | x16 rayons : le calcul devient plus gourmand. |
| 6a. PPM binaire (P6) | ~172 ms | ~1,4 ms | Écriture 10 fois plus rapide, fichier de 270 015 octets (400 x 225 x 3 + en-tête). |
6b. Parallélisme (rayon) |
~75 ms | ~1,8 ms | Calcul environ 2 fois plus rapide (x2,3 mesuré). |
Scène
vitrineen 1920 x 1080 (8 objets, 16 rayons par pixel, réflexions, 2 coeurs) : environ 3,4 s de calcul.
Temps propre de chaque fonction, mesuré avec perf et un flamegraph (après inlining) :
| Fonction | Temps | Contenu inliné |
|---|---|---|
couleur_rayon |
36 % | ombres, éclairage, réflexion |
plus_proche |
32 % | tests d'intersection rayon/objet |
closure de rendu |
25 % | caméra, suréchantillonnage, 1er niveau de couleur_rayon |
| noyau | ~2 % | allocations et mémoire |
En cumulé :
intersecte: 29 % du temps totaldans_l_ombre: 27 % (note : appelle lui-mêmeintersecte)sqrt: 8,5 %
- Caméra fixe avec un angle de vue large, ce qui provoque une déformation sur les bords.
- Une seule source de lumière ponctuelle, produisant des ombres dures (sans pénombre).
- Pas de réfraction (verre), pas de textures, pas de maillages triangulaires.
- Anti-aliasing uniforme : 16 rayons sont tirés partout, y compris sur les zones plates (ciel, sol uni).
- Anti-aliasing adaptatif : ne tirer 16 rayons que sur les contrastes/bords et 1 seul sur les zones unies.
- Caméra avec plus de paramètre : position, cible regardée, champ de vision.
- Matériaux et textures : réfraction (loi de Snell-Descartes), damier procédural, triangles.
- Structure accélératrice (BVH) : arbre de boîtes englobantes pour gérer des milliers d'objets sans tester chaque rayon contre chaque primitive.
Distribué sous licence MIT. Voir le fichier LICENSE pour plus de détails.