Raytracer docs 🌐 EN

Accueil / Primitives

Primitives

Les primitives sont les surfaces géométriques que les rayons viennent frapper. Chaque type ci-dessous correspond à une classe qui implémente l'interface IPrimitive et à une valeur de la clé "type" dans le fichier de scène JSON. Le contenu de cette page reflète le code réel (srcs/plugins/primitive/, PrimitiveBuilder, SceneParser).

10 primitives IPrimitive Intersection analytique & marching AABB / BVH

Qu'est-ce qu'une primitive ?

Une primitive est un objet géométrique intersectable par un rayon. Toutes les primitives implémentent l'interface commune IPrimitive, qui expose trois comportements essentiels :

  • Test d'intersection — étant donné un rayon P(t) = O + tD, la primitive calcule le plus petit paramètre t valide (dans l'intervalle [t_min, t_max]) pour lequel le rayon rencontre sa surface, ou signale l'absence de contact.
  • Normale — au point d'impact H = O + t·D, la primitive fournit la normale de surface N, orientée face au rayon (retournée si D·N > 0). La normale sert à l'ombrage, aux réflexions et aux réfractions.
  • Boîte englobante (AABB) — chaque primitive expose une boîte alignée sur les axes qui la contient entièrement. Cette boîte permet au BVH de rejeter très vite les rayons qui manquent l'objet. Voir AABB & BVH.

Propriétés communes

Dans le fichier de scène, chaque primitive partage un socle de clés communes ; les autres clés dépendent du type (rayon, hauteur, sommets…).

CléTypeRôle
namechaîneNom de l'objet (identifiant affiché dans l'éditeur).
typechaîneType de primitive (sphere, plane, …).
position[x, y, z]Position dans le monde ; place le centre de la primitive.
rotation[x, y, z]Rotation d'Euler en degrés (appliquée X, puis Y, puis Z).
scale[x, y, z]Facteur d'échelle par axe (non uniforme autorisé).
materialref matériauNom d'un matériau déclaré dans la liste materials. Voir Matériaux.

Toutes les primitives n'utilisent pas toutes ces clés : par exemple le plan est orienté par un axe plutôt que par rotation, et le triangle est défini par ses trois sommets plutôt que par position/scale. Chaque section précise les clés réellement lues par le parseur.

Sphère

type: "sphere" — l'ensemble des points situés à distance constante r d'un centre C. C'est la primitive analytique de base.

Intersection — en substituant le rayon dans l'équation implicite ‖P − C‖² − r² = 0, on obtient une équation quadratique a·t² + b·t + c = 0 avec a = D·D, b = 2·(oc·D), c = oc·oc − r² et oc = O − C. Le discriminant Δ = b² − 4ac décide : Δ < 0 aucun contact, sinon on garde la plus petite racine valide. La normale est N = normalize(H − C).

CléTypeDescription
position[x, y, z]Centre de la sphère.
radiusnombreRayon r (> 0).
scale[x, y, z]Échelle appliquée à la sphère.
materialref matériauMatériau de surface.
{
    "name": "Boule",
    "type": "sphere",
    "position": [0.0, 1.0, 0.0],
    "scale": [1.0, 1.0, 1.0],
    "radius": 1.0,
    "material": "Rouge"
}

Plan

type: "plane" — une surface plane, orientée selon un axe aligné sur le repère (x, y ou z). La clé axis est convertie en vecteur normal via AxisUtils::toVector ; le plan passe par position.

Intersection — le plan résout directement (P − P₀)·N = 0, ce qui donne t = ((P₀ − O)·N) / (D·N). Si le dénominateur D·N est proche de zéro, le rayon est parallèle au plan et il n'y a pas de contact. La normale est constante en tout point, égale à l'axe (retournée face au rayon).

CléTypeDescription
axis"x" / "y" / "z"Axe d'orientation du plan (sa normale).
position[x, y, z]Point par lequel passe le plan.
sizenombreÉtendue du plan.
materialref matériauMatériau de surface.
{
    "name": "Sol",
    "type": "plane",
    "axis": "y",
    "position": [0.0, 0.0, 0.0],
    "size": 100.0,
    "material": "Damier"
}

Cube

type: "cube" — un polyèdre convexe fini à 6 faces, construit comme un cube orienté (position + rotation + scale) à partir de 6 faces planes de côté size.

Intersection — le rayon est testé contre chacune des 6 faces (chaque face est un plan borné) ; on retient le plus petit t valide. Un point d'impact candidat est accepté seulement s'il tombe dans les bornes de la face (0 ≤ u ≤ 1, 0 ≤ v ≤ 1 dans la base locale de la face). La normale est la normale axiale de la face gagnante (±X, ±Y, ±Z), corrigée par la rotation et l'échelle inverse.

CléTypeDescription
position[x, y, z]Centre du cube.
rotation[x, y, z]Rotation d'Euler en degrés.
scale[x, y, z]Échelle non uniforme.
sizenombreLongueur d'arête de base.
materialref matériauMatériau de surface.
{
    "name": "Boite",
    "type": "cube",
    "position": [0.0, 1.0, 0.0],
    "rotation": [0.0, 45.0, 0.0],
    "scale": [1.0, 1.0, 1.0],
    "size": 2.0,
    "material": "Bois"
}

Un cube peut être converti exactement en maillage de 8 sommets / 12 triangles pour l'édition de sommets (voir Édition de sommets).

Cylindre

type: "cylinder" — un cylindre circulaire droit défini par un axe, un rayon radius et une hauteur finie height.

Intersection — on projette rayon et origine perpendiculairement à l'axe (Dperp, ocPerp), ce qui ramène la surface latérale à une équation quadratique a·t² + b·t + c = 0 avec a = Dperp·Dperp, b = 2·(ocPerp·Dperp), c = ocPerp·ocPerp − r². Une racine n'est retenue que si la projection du point sur l'axe reste dans [0, h]. La normale au point H est normalize(H − Q), où Q est la projection de H sur l'axe.

CléTypeDescription
position[x, y, z]Centre du cylindre.
rotation[x, y, z]Orientation (Euler, degrés).
scale[x, y, z]Échelle.
radiusnombreRayon r.
heightnombreHauteur finie h.
materialref matériauMatériau de surface.
{
    "name": "Colonne",
    "type": "cylinder",
    "position": [0.0, 0.0, 0.0],
    "rotation": [0.0, 0.0, 0.0],
    "scale": [1.0, 1.0, 1.0],
    "radius": 0.5,
    "height": 3.0,
    "material": "Marbre"
}

Cône

type: "cone" — un cône circulaire droit défini par un apex, un axe et une ouverture, limité à une hauteur finie height.

Intersection — comme le cylindre, on décompose le rayon en composantes parallèle et perpendiculaire à l'axe, mais l'équation implicite fait intervenir la tangente de l'angle (k) : ‖v_perp‖² − k²·v_parallel² = 0. Cela produit une quadratique avec a = Dperp·Dperp − k²·dv², b = 2·(ocPerp·Dperp − k²·ov·dv), c = ocPerp·ocPerp − k²·ov². Les racines sont clippées à [0, h] le long de l'axe. La normale dérive du gradient de la fonction implicite.

CléTypeDescription
position[x, y, z]Centre du cône.
rotation[x, y, z]Orientation (Euler, degrés).
scale[x, y, z]Échelle.
radiusnombreRayon de base.
heightnombreHauteur du cône.
materialref matériauMatériau de surface.
{
    "name": "Cornet",
    "type": "cone",
    "position": [0.0, 0.0, 0.0],
    "rotation": [0.0, 0.0, 0.0],
    "scale": [1.0, 1.0, 1.0],
    "radius": 1.0,
    "height": 2.0,
    "material": "Orange"
}

Triangle

type: "triangle" — une primitive planaire finie définie par ses trois sommets vertex0, vertex1, vertex2. C'est la brique de base de la géométrie triangulée et une primitive éditable (ses 3 coins peuvent être déplacés dans le viewport).

Intersection — algorithme de Möller–Trumbore. À partir des arêtes E1 = V1 − V0 et E2 = V2 − V0, on résout le système barycentrique et on obtient (u, v, t) : on rejette si u < 0, v < 0 ou u + v > 1. La normale plate est normalize(E1 × E2) (ou interpolée par les normales de sommets pour un ombrage lisse).

CléTypeDescription
vertex0[x, y, z]Premier sommet V0.
vertex1[x, y, z]Deuxième sommet V1.
vertex2[x, y, z]Troisième sommet V2.
materialref matériauMatériau de surface.
{
    "name": "Facette",
    "type": "triangle",
    "vertex0": [0.0, 0.0, 0.0],
    "vertex1": [1.0, 0.0, 0.0],
    "vertex2": [0.0, 1.0, 0.0],
    "material": "Vert"
}

Tore

type: "torus" — une surface en forme d'anneau (donut), définie par un grand rayon radius (R, distance du centre à l'axe du tube) et un petit rayon height (r, rayon du tube).

Intersection — après passage dans le repère local du tore, la surface implicite quartique (‖P‖² + R² − r²)² − 4R²(…) = 0 se ramène à une équation de degré 4 c₄t⁴ + c₃t³ + c₂t² + c₁t + c₀ = 0. On résout le quartique et on garde la plus petite racine réelle valide. La normale est le gradient de la fonction implicite, ramené dans le repère du monde.

Attention à la convention : ici radius est le grand rayon R et height le rayon du tube r.

CléTypeDescription
position[x, y, z]Centre du tore.
rotation[x, y, z]Orientation (Euler, degrés).
radiusnombreGrand rayon R.
heightnombreRayon du tube r.
materialref matériauMatériau de surface.
{
    "name": "Anneau",
    "type": "torus",
    "position": [0.0, 1.0, 0.0],
    "rotation": [90.0, 0.0, 0.0],
    "radius": 1.0,
    "height": 0.3,
    "material": "Or"
}

Tanglecube

type: "tanglecube" — une surface algébrique implicite (isosurface) lisse, engendrée par un champ scalaire quartique et un seuil threshold : f(p) = x⁴ − 5x² + y⁴ − 5y² + z⁴ − 5z² + threshold, la surface étant l'ensemble f(p) = 0.

Intersection — plutôt qu'une résolution analytique du quartique, l'implémentation procède par marching : intervalle d'entrée/sortie via une sphère englobante, progression à pas adaptatif jusqu'à détecter un changement de signe g(t_prev)·g(t_cur) < 0, puis raffinement de la racine (Newton avec repli par bissection). La normale est normalize(∇f) avec ∇f = (4x³ − 10x, 4y³ − 10y, 4z³ − 10z).

CléTypeDescription
position[x, y, z]Centre de la surface.
rotation[x, y, z]Rotation (stockée sur l'objet).
thresholdnombreDécalage d'iso-valeur pour f(p) = 0.
sizenombreFacteur d'échelle global.
materialref matériauMatériau de surface.
{
    "name": "Tanglecube",
    "type": "tanglecube",
    "position": [0.0, 2.0, 0.0],
    "rotation": [0.0, 0.0, 0.0],
    "threshold": 11.8,
    "size": 1.0,
    "material": "Bleu"
}

Fractale

type: "fractal" — un objet fractal 3D (type Mandelbulb) rendu par estimation de distance (distance estimator / sphere tracing). La surface est définie implicitement par un estimateur DE(p) = 0 calculé de façon itérative.

Intersectionray marching : on borne d'abord le rayon par l'AABB de la primitive pour obtenir [tEnter, tExit], puis on avance par pas égaux à la distance estimée d = DE(p)·size. Quand d < ε, le point est accepté ; sinon t += d, jusqu'à sortir de la boîte ou atteindre la limite d'itérations. La normale est estimée numériquement par différences finies sur DE autour du point.

CléTypeDescription
position[x, y, z]Centre de la fractale.
sizenombreMise à l'échelle du domaine fractal (défaut 100).
powernombreExposant fractal (défaut 8).
iterationsentierNombre d'itérations dans l'estimateur (défaut 8).
materialref matériauMatériau de surface.
{
    "name": "Mandelbulb",
    "type": "fractal",
    "position": [0.0, 0.0, 0.0],
    "size": 100.0,
    "power": 8.0,
    "iterations": 8,
    "material": "Violet"
}

Maillage

type: "mesh" — une surface triangulée chargée depuis un fichier Wavefront .obj (via ObjParser), ou définie en ligne par des tableaux vertices / faces / normals. Le maillage est une seule primitive qui possède en interne ses triangles et un BVH local, ce qui permet d'intersecter des centaines de milliers de triangles en temps logarithmique. Il est éditable au sommet.

Intersection — chaque triangle est testé par Möller–Trumbore, mais les rayons ne parcourent jamais les triangles linéairement : le BVH local les élague. Les normales sont interpolées par coordonnées barycentriques quand le .obj fournit des normales de sommet (vn), sinon une normale de face plate sert de repli. La géométrie est recentrée sur son propre centre : position place donc le centre du maillage.

Quand une géométrie en ligne est fournie (vertices non vide), elle a la priorité sur le chemin file : c'est ainsi que sont stockées les primitives « cuites » (converties en maillage).

CléTypeDescription
filechaîneChemin du .obj (résolu depuis le dossier de travail).
verticesliste [x, y, z]Géométrie en ligne (prioritaire sur file).
facesliste [i, j, k]Faces triangulaires (indices de sommets).
normalsliste [x, y, z]Normales de sommet (optionnelles).
position[x, y, z]Position du centre du maillage.
rotation[x, y, z]Rotation d'Euler en degrés.
scale[x, y, z]Échelle par axe (non uniforme autorisée).
vertex_overrideslisteSurcharges de sommets (index + position) réappliquées après chargement du .obj.
materialref matériauMatériau appliqué à tous les impacts.
{
    "name": "Suzanne",
    "type": "mesh",
    "file": "tests/obj/suzanne.obj",
    "position": [0.0, 0.0, 0.0],
    "rotation": [0.0, 0.0, 0.0],
    "scale": [1.0, 1.0, 1.0],
    "material": "Bleu"
}

Seules les faces triangulaires sont acceptées ; les coordonnées de texture (vt) et les bibliothèques de matériaux (mtllib / usemtl) du .obj sont ignorées. Détails et édition : Fonctionnalités → Maillages .obj.

AABB & BVH

Une AABB (Axis-Aligned Bounding Box) est une boîte alignée sur les axes, définie par ses coins minimum Bmin et maximum Bmax. Ce n'est pas une primitive que l'on place dans une scène : c'est le volume englobant exposé par chaque primitive et exploité par le BVH.

Le test rayon/AABB se fait par la méthode des slabs : pour chaque axe on calcule les paramètres d'entrée et de sortie t1 = (Bmin − O)/D, t2 = (Bmax − O)/D, puis on combine tEnter = max(tNear) et tExit = min(tFar). Il y a intersection lorsque tEnter ≤ tExit. Un rayon qui manque la boîte d'un nœud ignore tout son sous-arbre.

Avant le rendu, la scène construit une hiérarchie de volumes englobants (BVH) via scene.buildBvh() ; chaque nœud porte une AABB. Les maillages disposent en plus d'un BVH local sur leurs triangles. Le détail de la construction est décrit dans Fonctionnalités → Accélération BVH.

Le dépôt contient aussi les fichiers source Mobius_strip.*, mais cette primitive n'est pas branchée au parseur ni au builder : elle n'est pas disponible en scène.