-
Notifications
You must be signed in to change notification settings - Fork 0
The optics
Every law the engine implements, the convention it is written in, and the test that pins it. Where a formula is a simplification, this page says so plainly. The point of the engine is that you can tell the difference.
Source: source/linalg.c,
source/polar.c,
source/spectrum.c,
source/geometry.c.
hv3_reflect(d, n) = d − 2(d·n)n
d arrives (unit, pointing into the surface), n is the surface normal on
d's side. Length is preserved exactly.
Pinned by: 45° in gives 45° out; normal incidence returns along itself; the result stays unit. Also, at the scene level, the mirror-image property: looking through a perfect mirror at an object equals looking directly at that object's mirrored position, which catches a sign error the single-surface tests would not.
hv3_refract(d, n, eta, &out) eta = n_from / n_to
k = 1 − eta²(1 − cos²θi) cosθi = −n·d
out = eta·d − (eta·(−cosθi) + √k)·n
Returns 0 when k < 0: the transmitted angle would need sin > 1, which is
total internal reflection by definition, and the caller must reflect instead.
Pinned by: 30° into n=1.5 glass emerging at 19.4712° (x-component exactly 1/3); normal incidence passing straight through at any eta; a ray at 41° inside n=1.5 glass escaping at the Snell angle while one at 43° is trapped, bracketing the 41.8103° critical angle from both sides.
The real pair, not Schlick's approximation. holo_fresnel() returns power
reflectances; holo_fresnel_amp() returns amplitudes plus the machinery
polarization needs.
Rs = |(n₁cosθi − n₂cosθt) / (n₁cosθi + n₂cosθt)|²
Rp = |(n₁cosθt − n₂cosθi) / (n₁cosθt + n₂cosθi)|²
holo_fresnel_amp() additionally returns the transmission amplitudes ts,
tp, and the power-projection factor
f = (n₂cosθt) / (n₁cosθi) so that f·ts² = 1 − rs²
which is what makes the transmitted Mueller matrix conserve energy without a fudge factor.
Past the critical angle the function returns 1 (TIR) and reports the phase difference the reflection imposes:
δ = δp − δs = 2·[ atan(g / (n²cosθi)) − atan(g / cosθi) ]
n = n₂/n₁ < 1, g = √(sin²θi − n²)
Pinned by: 4% at normal incidence on n=1.5; a vanishing p-component at
Brewster's angle atan(1.5) = 56.31°, where the reflection is completely
polarized (degree of polarization exactly 1); reciprocity across the interface
at the paired Snell angle; Rs = Rp = 1 past the critical angle; R + T = 1
per polarization; and δ = 36.9° for glass-to-air at 45°, the number a Fresnel
rhomb is cut to exploit.
Sources are unpolarized, so where the engine needs a single scalar it uses
(Rs + Rp)/2. The two are kept separate everywhere else because polarization
needs them apart.
Every source in hologram is unpolarized, so a camera path never needs the
full 4×4 Mueller product, only its first row. Each spectral ray therefore
carries a HoloSRow (the accumulated detector row, four floats) plus a
reference frame: the unit transverse vector its Q axis is measured against.
srow' = srow · M
When a path reaches a source of intensity S, the camera sees S · srow.i.
holo_srow_rotate(s, c2, s2) |
Frame rotation, given as the double angle (cos 2θ, sin 2θ). |
holo_srow_mueller(s, a, b, c, d) |
The interface template [a b 0 0; b a 0 0; 0 0 c d; 0 0 −d c]. Fresnel reflection and transmission, retarders and mirrors are all this shape in their own basis. |
holo_srow_polarizer(s) |
Ideal linear polarizer along the frame. |
holo_frame_rot(frame, target, dir, &c2, &s2) |
The double angle rotating one frame onto another about dir, from two dot products, with no atan in the hot path. |
The s vector of the plane of incidence, ŝ = normalize(d × n), is the
basis both Fresnel branches speak. The walk rotates the ray's frame onto ŝ,
applies the Mueller matrix built from the Fresnel amplitudes, and carries ŝ
forward as the new frame. At normal incidence there is no plane of incidence
and no rotation to perform, so the frame is carried unchanged.
Reflection uses a = ½(rs²+rp²), b = ½(rs²−rp²), c = rs·rp; transmission
uses the same shape scaled by f. Under TIR, a = 1 with c = cos δ and
d = sin δ, the retardance above, which is how linear light entering a
Fresnel rhomb comes out elliptical.
Pinned by: Malus's law at five angles (0°, 30°, 45°, 60°, 90°, giving
½cos²θ); the three-polarizer paradox (crossed = 0, a 45° third between them =
exactly 0.125); Brewster giving degree of polarization 1; energy conservation
per polarization; quarter- and half-wave plates between crossed polarizers
transmitting ½sin²(δ/2); and TIR's 36.9° retardance.
Grating orders are weighted by polarization-neutral scalars. A real grating's efficiency is strongly polarization-dependent, and computing it rigorously is a solver's job (see gratinglab) rather than a renderer's.
HOLO_WAVELENGTHS = 12 samples, evenly spaced 0.42 to 0.68 µm. Fixed, not
stochastic: the CPU and GPU must trace identical rays for the oracle diff to
mean anything, and a game that renders the same still frame twice should not
shimmer.
Cauchy's equation, anchored at the sodium D line so that n(λ_D) = ior
exactly, for any B:
n(λ) = n_d + B · (1/λ² − 1/λ_D²) λ_D = 0.5893 µm
B = 0 is achromatic glass. BK7 is roughly n_d = 1.5168, B = 0.0042; dense
flints run several times that.
Pinned by: the D line returning the quoted index exactly; n_F > n_d > n_C;
and the Abbe number (n_d − 1)/(n_F − n_C) computing to 64.4 for those BK7
coefficients, against a catalogue value of about 64.2.
Intensities are folded to linear sRGB through the Wyman–Sloan–Shirley piecewise-Gaussian fits to the CIE 1931 colour matching functions (JCGT 2013), then through the standard XYZ→sRGB (D65) matrix, then normalized per channel so that a flat spectrum lands on exact white. Hue structure comes from the eye; the white point is the engine's choice.
The CPU computes these weights and ships them to the shader in the uniform block. The shader must not re-derive them, because both sides folding the same floats is part of what the oracle certifies.
Scene colours stay RGB. An albedo is read at a wavelength through three smooth
bands that partition unity (holo_albedo_at), so neutral colours are exact
at every wavelength and a gray scene renders identically through the RGB and
spectral pipelines. A saturated red albedo will not survive the round trip to
the exact same red; that path is not colorimetry. The physics being showcased
(dispersion angles, order directions, retardance) does not pass through it at
all.
A dish is a cap of a conic of revolution, in the parameters optical design quotes: apex, axis (unit, out of the bowl), vertex radius of curvature R, conic constant K, rim radius.
The sag equation cleared of its square root is quadratic along a ray, which is why the intersection is closed form:
x² + y² + (1+K)z² − 2Rz = 0 in the dish's frame, apex at the origin
| K | Surface |
|---|---|
| 0 | Sphere |
| −1 | Paraboloid |
| −e², −1 < K < 0 | Ellipsoid |
| < −1 | Hyperboloid |
The normal is the gradient, (x, y, (1+K)z − R), taken back into world axes.
The cap is clipped at the rim's sag, which is what keeps an ellipsoid's far
half and a hyperboloid's second sheet out of the scene. An axis-parallel ray
on a paraboloid degenerates the quadratic to a line; that case is handled
explicitly.
Pinned by: a paraboloid with R = 2 reflecting parallel rays at three different zone radii through the focus at exactly R/2, a test that catches a wrong normal as surely as a wrong sag; an ellipsoid (a = 2, e = 0.5, so R = 1.5, K = −0.25) imaging its near focus onto its far one at three angles; and both properties surviving an arbitrary rotation of the dish's frame.
A backward tracer cannot show a caustic by tracing camera rays alone. What it can show is the focal property itself: give the sky a sun disk and stand at a paraboloid's focus, and every point of the dish reflects the eye ray into the sun, so the whole aperture blazes. That is exactly what looking into a real solar furnace from its focus does.
Implemented in the conical (off-plane) vector form, of which the classical in-plane mount is the special case.
Let ĝ be the groove direction (unit, in the surface), n̂ the normal, and
q̂ = ĝ × n̂ the in-plane dispersion direction. For incident direction d:
α = d·q̂ + mλ/d_spacing the dispersion component picks up the order
β = d·ĝ the groove component is CONSERVED
out = α·q̂ + β·ĝ + √(1 − α² − β²)·n̂
Returns 0 when 1 − α² − β² ≤ 0: the order is evanescent and does not
propagate. m = 0 reduces to exact specular reflection, at any skew.
The conservation of β is the conical invariant, the reason an off-plane mount
disperses along a cone rather than a plane, and the property the in-plane
formula sin θm = sin θi + mλ/d hides by setting β = 0.
Pinned by: m = 0 equalling hv3_reflect component by component at a
skew incidence; β conserved and the output unit for a diffracted order;
Littrow, where at sin θ = λ/2d the m = −1 order retroreflects exactly,
the alignment every grating lab uses; longer wavelengths diffracting further;
and an order going evanescent past 90°. At the scene level, an energy audit:
three orders propagate at 550 nm through a 1 µm grating, and the second joins
the sum at 450 nm, exactly where the equation admits it.
Order efficiencies are hand-set scalars per grating, not computed. Real efficiency depends on groove profile, blaze angle, coating and polarization, and getting it right means a rigorous solver (integral method, RCWA, C-method). The renderer needs the equation; the efficiencies are art direction.
Stated so nobody mistakes an omission for a claim:
- Caustics and indirect light. No photon mapping, no bidirectional paths. Light reaches the camera along camera rays only; a glass ball casts no bright spot, and mostly-clear glass is given no hard shadow rather than a wrong black disc.
- Interference between paths. Stokes vectors carry polarization, not optical path length. Thin-film colour, Newton's rings and speckle are out of reach; a waveplate's colour comes from its 1/λ retardance, which is a single-element effect.
- Diffraction as a wave phenomenon. Gratings are handled by the grating equation, which tells you where orders go, rather than by solving Maxwell's equations. No Airy disc, no edge diffraction, no aperture-limited resolution.
- Absorption with depth. Glass tint is applied per interface, not by Beer–Lambert through path length.
- Curved refractive surfaces. Dishes are mirror or matte; there are no lenses with conic figure yet. Refraction happens at spheres (volumes) and rectangles (thin panes).
Reference
Guides
History