Aller au contenu principal

Dispersion par Prisme

Ouvrir l'expérience du prisme. Faites pivoter le prisme triangulaire équilatéral à la souris ou avec les touches fléchées. Le tiroir de paramètres permet d'ajuster l'angle de rotation et la dispersion de Cauchy, ou de rétablir les réglages de référence en un clic.

Modèle d'Optique Géométrique​

La simulation suit, dans un plan, les rayons d’un faisceau blanc traversant un prisme équilatéral. Le calcul applique les lois de Snell-Descartes sous forme vectorielle pour chaque rayon incident, détectant la réfraction et la réflexion totale interne (TIR).

Lois vectorielles de réfraction et de réflexion​

Soient v\mathbf{v} la direction unitaire du rayon incident, n\mathbf{n} la normale extérieure à l’interface et η=n1/n2\eta = n_1 / n_2 le rapport des indices des deux milieux.

  1. Orienter la normale dans le sens opposé au rayon incident, de sorte que cos⁡θi=−(v⋅n)>0\cos \theta_i = -(\mathbf{v} \cdot \mathbf{n}) > 0.

  2. Calculer le discriminant de transmission :

    Δ=1−η2(1−cos⁡2θi).\Delta = 1 - \eta^2 (1 - \cos^2 \theta_i).
  3. Réflexion totale interne (TIR) : si Δ<0\Delta < 0, aucune transmission n’est possible. La direction réfléchie est :

    v′=v−2(v⋅n)n.\mathbf{v}' = \mathbf{v} - 2(\mathbf{v} \cdot \mathbf{n})\mathbf{n}.
  4. Transmission réfractée : si Δ≥0\Delta \ge 0, la direction transmise est :

t=ηv+(ηcos⁡θi−1−η2(1−cos⁡2θi))n.\mathbf{t} = \eta \mathbf{v} + \left(\eta \cos \theta_i - \sqrt{1 - \eta^2 (1 - \cos^2 \theta_i)}\right)\mathbf{n}.
export function refract(direction, outwardNormal, fromIndex, toIndex) {
const normal = dot(direction, outwardNormal) > 0 ? outwardNormal.map(x => -x) : outwardNormal;
const cosine = -dot(direction, normal);
const ratio = fromIndex / toIndex;
const discriminant = 1 - ratio * ratio * Math.max(0, 1 - cosine * cosine);
if (discriminant < 0) return null; // Réflexion totale interne
return unit(direction.map((x, i) => ratio * x + (ratio * cosine - Math.sqrt(discriminant)) * normal[i]));
}

Modèle de dispersion de Cauchy et échantillonnage des longueurs d’onde​

Pour décomposer la lumière blanche en un spectre continu, l’indice de réfraction varie avec la longueur d’onde λ∈[400,700] nm\lambda \in [400, 700]\,\text{nm} selon la formule empirique de Cauchy :

n(λ)=1,5+B⋅0,008(1(λ/1000)2−10,552),n(\lambda) = 1{,}5 + B \cdot 0{,}008 \left(\frac{1}{(\lambda / 1000)^2} - \frac{1}{0{,}55^2}\right),

BB règle l’intensité de la dispersion ; quelle que soit sa valeur, l’indice de réfraction reste fixé à n(550 nm)=1,5n(550\,\text{nm})=1{,}5. Les 101 longueurs d’onde sont uniformément réparties entre 400 nm (violet) et 700 nm (rouge). Chacune comporte 21 rayons parallèles, soit 101×21=2 121101 \times 21 = 2\,121 rayons au total, avec au plus 12 interactions par rayon.

export function traceRay(origin, direction, polygon, index, width, height) {
const points = [origin];
let p = origin, v = direction;
for (let bounce = 0; bounce < 12; bounce++) {
const hit = intersectRay(p, v, polygon);
if (!hit) {
// Le rayon sort du prisme : prolonger jusqu’au bord de la vue
points.push(extendToBoundary(p, v, width, height));
break;
}
points.push(hit.point);
const exiting = dot(v, hit.normal) > 0;
const transmitted = refract(v, hit.normal, exiting ? index : 1, exiting ? 1 : index);
v = transmitted ?? v.map((x, i) => x - 2 * dot(v, hit.normal) * hit.normal[i]);
p = hit.point.map((x, i) => x + v[i] * 1e-4);
}
return points;
}

Dans Bending Light de PhET, ouvrez l’écran Prisms, placez un prisme triangulaire dans le faisceau, choisissez la lumière blanche et allumez la source. Changez le milieu ou l’angle du prisme pour relier la réfraction à chaque interface à la séparation des couleurs observée ici.

Optimisation GPU avec p5.js WebGL​

Avec un contexte Canvas 2D, tracer chaque rayon séparément demanderait 2 121 appels de tracé par redessin, ainsi que la composition des couleurs. La rastérisation de Canvas 2D peut utiliser le CPU ou le GPU, selon le navigateur et la configuration du contexte. Ici, la géométrie des faisceaux est soumise par lots à WebGL pour réduire les appels de dessin. Le moteur actuel convertit toutes les coordonnées de faisceaux dans un tampon Float32Array unique (BeamMesh) :

PropriétéPipeline Canvas 2DPipeline WebGL par lots
Géométrie2 121 objets Path2D distinctsun tampon GPU Float32Array alloué en une fois
Appels de dessin2 121 appels par redessinun seul appel pour les faisceaux, puis un pour le remplissage du prisme et un pour son contour
Adoucissement des bordstracé et anticrénelage de Canvas 2D par le navigateuratténuation gaussienne calculée dans le fragment shader à partir de la distance normale signée
Mélange des couleurscomposition Canvas du navigateurmélange additif matériel (gl.blendFunc(gl.ONE, gl.ONE))
// Chaque segment devient un rectangle : 2 triangles et 6 sommets
// Les sommets portent les coordonnées, la couleur prémultipliée et la distance normale
mesh.segment(p1, p2, [r, g, b, alpha], lineWidth);

// Un seul bufferSubData transfère tous les faisceaux, suivis d’un seul appel de dessin
gl.bufferSubData(gl.ARRAY_BUFFER, 0, mesh.data.subarray(0, mesh.count * VERTEX_SIZE));
gl.drawArrays(gl.TRIANGLES, 0, beamVertexCount);

L'ensemble des faisceaux est tracé en un seul appel de rendu GPU avec mélange additif et anticrénelage dans le fragment shader. Le fragment shader calcule l’adoucissement gaussien à partir de la distance normale signée transmise par les sommets, sans échantillonner de texture.

Découplage des interactions et restauration du contexte​

  • Interactions et état React : le glissement modifie directement l’angle dans une référence interne et redessine à la demande dans requestAnimationFrame, évitant de relancer le rendu React à 60 FPS. Les contrôles de paramètres ne sont synchronisés qu’à la fin du geste.
  • Perte du contexte WebGL : les événements webglcontextlost et webglcontextrestored permettent de libérer puis de réinitialiser les shaders lors d’un changement d’écran sur mobile ou d’une réaffectation des ressources GPU, sans fuite de mémoire.
Explorer les liensOuvrir le réseau