Self-shadowing, secondary illumination, surface albedo maps.
Complete (2026-10-17, cc70cea). All nine issues landed, and the C1/C2 checkpoint is in the readme. Each issue's comment has its measurements and limitations.
| Step | Issue | Commit |
|---|---|---|
| Pixel-area sampling | #52 | 4d070db |
| Gaussian PSF | #23 | d0cec65 |
| Brown-Conrady distortion | #22 | 49e0500 |
| Apparent positions | #51 | e0381cc |
| Stars | #28 | 970551b |
| Shot, read and dark noise | #25 | 1ed9598 |
| PRNU, DSNU and hot pixels | #26 | 3d0b129 |
| Blooming | #27 | 7e1b64c, fbd7ce4 |
| Airy and measured PSFs by FFT (C2) | #24 | 5e2769f |
| C1/C2 checkpoint | cc70cea |
Where the tiers ended up (1920 x 1080 RGB, submit to wait_view, box-wing to ICESat-2):
- L1 / C1: 4.1-5.2 ms on the RTX 3060, 83-684 ms on lavapipe.
- L1 / C2: 35-37 ms on the RTX 3060, 0.61-1.22 s on lavapipe. The FFT convolution on a 4096 x 4096 grid is 30 ms of it.
Decided along the way
- The FFT PSF became tier C2 rather than part of C1 (2026-10-17): it costs about 30 ms on the GPU. C1 keeps the separable Gaussian and refuses Airy and measured PSFs.
- The gallery and the showcase movie render at camera C1. The movie's auto exposure is capped at the flight time between frames and changes by at most 6x per second of flight.
- The long-exposure example scenes use a calibrated camera (
hot_pixels = [1e-6, 50]).
Left for later
- C2's f32 round-off floor with the Sun in frame, about 10 e- RMS: the separate path for the brightest sources (#24's comment).
- A real-input FFT for the unpaired third channel, about 25 % off the C2 pass.
- The pre-push hook is at 33 s warm, over the 30 s target below.
Optics and detector effects: camera tiers C1 and C2. Each issue holds its own design, tests and measurements. This is the plan across them, decided with Connor on 2026-10-15.
Tiers, split by cost
-
C1: the cheap camera effects, all of them.
- #52 pixel-area sampling;
- #23 Gaussian PSF;
- #22 Brown-Conrady distortion;
- #51 apparent positions;
- #28 stars (landed as part of lighting L1, shown only from C1, decided 2026-10-16);
- #25 shot, read and dark noise;
- #26 PRNU and DSNU;
- #27 blooming.
Noise-free images stay available at C1 through the camera's parameters (zero read noise and dark current, shot noise off), not through a tier.
-
C2: the expensive optics. #24, Airy and measured PSFs by FFT, for now; later costly camera effects join it.
-
C0 is unchanged: pinhole, one sample per pixel, no blur or noise.
-
CameraTier::C1is introduced by the first C1 issue (#52) and documented as growing as M3 lands, the way L1 was in M2.
Order
- #52 pixel-area sampling. Introduces C1 and the first image-plane pass, the MSAA resolve. Fixes the gallery's moiré (see its comment).
- #23 Gaussian PSF. Separable convolution on radiance. Introduces the render margin that #22 and #24 share.
- #22 distortion.
CameraConfig::distortion,project/raythat include it, and one warp pass on the undistorted frame. It lands before the stars so they are placed through it. - #51 apparent positions.
Scene::observer_velocity, light time and aberration. Earth's own aberration (up to about 0.25 px in LEO) is still to be decided in the issue. - #28 stars. Landed in lighting L1, seen from camera C1. Hipparcos, Tycho-2 and Gaia DR3 to G 15 via a fetch script, with the faint limit set per run (both catalogs, agreed). A star-field example scene, and GEO-to-GEO telescope scenes for the gallery (Connor wants these).
- #25 noise. A seeded counter-based RNG; tests against the photon transfer curve.
- #26 PRNU and DSNU. Per-pixel maps from a camera seed or calibration frames.
- #27 blooming. Splits the sensor stage into electrons, then blooming, then noise and ADC.
- C1 checkpoint: a readme performance row and sample image for C1; switch the gallery (
scripts/make-gallery.sh) and the movie (crates/resolved-showcase) to C1. - #24 FFT PSF. Completes C2 for now; a readme row for C2.
Risks to measure early
- #24: f32 FFT round-off scales with the brightest pixel. The Sun at about 26,000 full wells could leave a floor of tens of electrons across the frame. Fallback: convolve the saturating sources separately.
- #22: the bilinear warp should keep edge positions to about 0.01 px. Check it at a limb before relying on it.
- Performance: each pass is measured on the RTX 3060 and lavapipe at 1920 x 1080 and 4k. The budget is high-fidelity RGB in about 10 ms on the GPU.
Working rules (as in M1-M2)
- One issue per change, left uncommitted for Connor to review and commit.
- After the commit, comment on the issue: what was done, measurements, limitations, what was tried first and why it was wrong. Leave the issue open.
- Update
docs/optics.mdfor readers who aren't optics specialists. - Zero warnings everywhere; plain prose; US English.
- Keep the pre-push hook under 30 s (29-30 s warm after #28; move slow GPU tests out of it before adding more).
Lighting: the atmosphere, clouds, a sharper Earth, materials and terrain. Each issue holds its own design, tests and measurements. This is the plan across them, decided with Connor on 2026-10-17.
Tiers, split by cost
- L1/C1 is the high-fidelity tier that still runs in under 10 ms on the GPU, aiming to stay under about 8 ms end to end (1920 x 1080 RGB,
submittowait_view). At the start of M4 it is 4.1-5.2 ms, of which the GPU's work is about 3.8 ms on the Landsat 8 scene: Earth and Moon 1.33 ms, blur 1.01, mesh 0.69, sensor 0.48, shadow 0.15, Earthshine 0.04. That leaves about 3 ms of GPU time for everything M4 adds to L1. - Each issue measures its cost and goes to L1 if it fits what is left of that budget. Otherwise it goes to L2, or splits: the cheap part in L1, the full version in L2.
- L2 holds the expensive lighting: volumetric clouds (#62), terrain (#33), finer settings for L1's methods (#55), and whatever does not fit L1.
Order (the atmosphere and clouds first; done: #59, #31)
- #59 gallery deltas. Before and after images of the example scenes between two commits, to review each change by eye. Small, and it helps with everything after it.
- #31 atmosphere: Earth's disk and limb. Precomputed scattering tables; replaces the haze stand-in in the bodies and Earthshine passes.
- #63 higher-resolution Earth. Blue Marble at about 460 m per texel, tiled and BC6H-compressed. Moved up (2026-10-17) after comparing the atmosphere with an ISS photograph: the coarse map was much of the difference.
- #60 sunlight through the atmosphere. The target reddening and dimming at orbital sunset, the Sun and Moon at the limb; possibly the eclipsed Moon's red glow (needs refraction).
- #32 cloud layer. A thin layer from a static global map, lit inside the atmosphere, with shadows on the ground; Earthshine sees it too.
- #61 cloud maps by date. An option, not the default: maps fetched ahead of time for the dates a run will request, never at render time.
- #62 volumetric clouds (L2).
- #56 anisotropic filtering and #57 pole artifacts, the rest of the map work.
- #50 Earthshine on the Moon's night side.
- #29 parametric BRDF, then #30 spectral materials.
- #33 Moon limb from LOLA terrain.
- #55 finer settings for L1's methods at L2.
- L1/L2 checkpoint: the readme rows and sample images, and the movie.
Gallery during the milestone
- From the first L2 feature,
scripts/make-gallery.shrenders L2 as well, and the gallery shows it. The gallery is the quickest way to see what each change adds, so it keeps up with each issue. The readme rows and the movie wait for the checkpoint.
GPU and CPU (#64)
- The contract stays "same shaders, same results, slower on the CPU". A feature becomes device-dependent only where a device cannot do it at all, not because it is slow there.
- A device that cannot run a setting falls back to the nearest lesser one and the renderer reports it; the CLI prints it. A setting asked for by name that the device cannot run is an error.
- GPU-against-CPU tests use settings both support. The readme gets a "GPU and CPU differences" section.
- Measured 2026-10-17: lavapipe's limits are per texture (16384 on a side, 128 MB per storage binding), not total memory (it uses system RAM); it has 2048-layer texture arrays and BC compression. So the sharper Earth (#63) may well run on both devices, tiled.
Risks to measure early
- #31: the cost of the limb glow, which needs work on pixels that miss Earth but cross its shell; and how many wavelengths the tables need for broad channels.
- #31 setup: building the tables on lavapipe.
- #63: memory (about 5 GB of BC6H with mips), setup time, and BC6H decoding speed on lavapipe.
- Performance: each pass on the RTX 3060 and lavapipe at 1920 x 1080, against the L1 budget above.
Working rules (as in M1-M3)
- One issue per change, left uncommitted for Connor to review and commit.
- After the commit, comment on the issue: what was done, measurements, limitations, what was tried first and why it was wrong. Leave the issue open.
- Update
docs/optics.mdfor readers who aren't optics specialists. - Zero warnings everywhere; plain prose; US English.
- Keep the pre-push hook near 30 s (33 s warm at the end of M3; move slow GPU tests out of it before adding many more).
Portable ray tracing and ML label outputs.
Absolute validation against references; remaining camera effects; larger outputs.