diff --git a/crates/resolved-showcase/src/main.rs b/crates/resolved-showcase/src/main.rs index 0f5f496..1229d36 100644 --- a/crates/resolved-showcase/src/main.rs +++ b/crates/resolved-showcase/src/main.rs @@ -1,9 +1,10 @@ //! `resolved-movie`: a showcase of the renderer, as a movie of a propagated flight. //! //! An inspector carrying the example camera flies around a Landsat 8 model in low -//! orbit, both propagated by astrokit with J2 (see [`flight`]), rendered at lighting L1: -//! Earth's surface turning below, the Moon rising over the limb, shadows moving across -//! the spacecraft and, at the end, the spacecraft passing into Earth's shadow. +//! orbit, both propagated by astrokit with J2 (see [`flight`]), rendered at lighting L1 +//! and camera C1: Earth's surface turning below, the Moon rising over the limb, shadows +//! moving across the spacecraft and, at the end, the spacecraft passing into Earth's +//! shadow. //! //! `--out movie.mp4` renders every frame and encodes them with ffmpeg. `--serve //! ADDRESS` renders live, in step with the wall clock, and streams the frames as MJPEG @@ -21,12 +22,14 @@ use std::time::{Duration, Instant}; use astrokit::prelude::Epoch; use clap::Parser; +use hifitime::{TimeScale, Unit}; use resolved::ephemeris::Ephemeris; use resolved::gpu::AdapterChoice; use resolved::nalgebra::Vector3; use resolved::{ - CameraConfig, Detector, EarthTexture, Fidelity, LightingTier, LoadOptions, Look, Mesh, - MoonTexture, Preview, Renderer, RendererOptions, Scene, Target, + CameraConfig, CameraTier, Detector, EarthTexture, Fidelity, LightingTier, LoadOptions, Look, + Mesh, MoonTexture, Preview, Renderer, RendererOptions, Scene, StarCatalog, StarPhotometry, + Target, }; type Result = std::result::Result; @@ -68,8 +71,8 @@ struct Cli { /// Device: auto, gpu, cpu, or part of an adapter's name. #[arg(long, default_value = "auto")] adapter: String, - /// Directory with earth/bmng-2004-MM.jpg and moon/wac-hapke-643.png. Without it, - /// the repository's data/. + /// Directory with earth/bmng-2004-MM.jpg, moon/wac-hapke-643.png and + /// stars/stars.bin. Without it, the repository's data/. #[arg(long, value_name = "DIR")] data: Option, /// Directory of astrokit's kernels. Without it: ASTROKIT_KERNELS, then @@ -121,23 +124,24 @@ fn load_almanac(explicit: Option<&Path>) -> Result> /// Auto exposure: after each frame, steer the next frame's exposure so the brightest /// parts of the image (its 99.9th percentile) sit at LEVEL of full scale (below half, /// since the pretty look lifts what is below it), changing by at most a factor of -/// STEP per second of movie, between the shortest exposure and one frame's time, as a -/// video camera is limited to. +/// STEP per second of flight, between the shortest exposure and the flight time from one +/// frame to the next, the longest a camera taking those frames could expose. const LEVEL: f64 = 0.45; const STEP: f64 = 6.0; const SHORTEST: f64 = 1e-5; +/// Before the first frame, the camera meters it up to this many times, until the +/// exposure changes by less than 5 %. +const METERING: usize = 16; /// Everything a frame needs, set up once. struct Movie { renderer: Renderer, preview: Preview, - /// The longest exposure, or None to keep it fixed. - auto_exposure: Option, - /// The most auto exposure changes from one frame to the next, as a factor. - step: f64, + /// Whether the exposure follows the image, or stays fixed. + auto_exposure: bool, detector: Detector, - /// One per frame, with its camera. - frames: Vec<(Scene, resolved::Pose)>, + /// One per frame, with its camera and the flight time to the next frame in seconds. + frames: Vec<(Scene, resolved::Pose, f64)>, } impl Movie { @@ -178,6 +182,24 @@ impl Movie { let mut camera = CameraConfig::example(); camera.exposure = cli.exposure; camera.focal_length = cli.focal_length; + // Flight time from each frame to the next; the last frame's is the one before it. + let mut spans: Vec = shots + .windows(2) + .map(|w| (w[1].epoch - w[0].epoch).to_seconds()) + .collect(); + spans.push(spans.last().copied().unwrap_or(1.0 / cli.fps as f64)); + // Only the stars the longest exposure can show. + let longest = if cli.fixed_exposure { + cli.exposure + } else { + spans.iter().fold(cli.exposure, |a, &b| a.max(b)) + }; + let faint = StarPhotometry::new(&camera).faintest_drawn(longest); + let years = (epoch - Epoch::from_gregorian_hms(2000, 1, 1, 12, 0, 0, TimeScale::TT)) + .to_unit(Unit::Day) + / 365.25; + let stars = StarCatalog::load(&data.join("stars/stars.bin"), years, faint) + .map_err(|e| format!("{e} (scripts/fetch-stars.py makes it)"))?; let adapter = match cli.adapter.as_str() { "auto" => AdapterChoice::Auto, "gpu" => AdapterChoice::Gpu, @@ -188,17 +210,19 @@ impl Movie { adapter, fidelity: Fidelity { lighting: LightingTier::L1, - ..Default::default() + camera: CameraTier::C1, }, earth_texture: Some(Arc::new(earth)), moon_texture: Some(Arc::new(moon)), + stars: Some(Arc::new(stars)), ..Default::default() }; let mut renderer = Renderer::new(camera, options).map_err(error)?; let model = renderer.load_model(&mesh).map_err(error)?; let frames = shots .iter() - .map(|shot| { + .zip(spans) + .map(|(shot, span)| { let target = Target { model, pose: shot.target, @@ -206,6 +230,7 @@ impl Movie { Ok(( ephemeris.scene(shot.epoch, vec![target]).map_err(error)?, shot.camera, + span, )) }) .collect::>>()?; @@ -228,8 +253,7 @@ impl Movie { Ok(Self { renderer, preview, - auto_exposure: (!cli.fixed_exposure).then_some(1.0 / cli.fps as f64), - step: STEP.powf(1.0 / cli.fps as f64), + auto_exposure: !cli.fixed_exposure, detector, frames, }) @@ -243,10 +267,28 @@ impl Movie { self.renderer.camera().height } - /// Frame `k` as 8-bit sRGB, and the next frame's exposure set from it. + /// Frame `k` as 8-bit sRGB, and the next frame's exposure set from it. The first + /// frame is metered before it is taken, so the movie does not open on a correction. fn render(&mut self, k: usize) -> Result> { - let (scene, camera) = &self.frames[k]; + if k == 0 && self.auto_exposure { + for _ in 0..METERING { + let before = self.renderer.camera().exposure; + self.expose(0, 0)?; + let ratio = self.renderer.camera().exposure / before; + if (ratio - 1.0).abs() < 0.05 { + break; + } + } + } + self.expose(k, k + 1) + } + + /// Frame `k` as 8-bit sRGB, with the exposure then set for frame `next`. + fn expose(&mut self, k: usize, next: usize) -> Result> { + let (scene, camera, _) = &self.frames[k]; let error = |e: resolved::Error| e.to_string(); + // A new draw of the detector's noise each frame. + self.renderer.set_frame_number(k as u64); let view = self .renderer .submit(scene, camera) @@ -256,10 +298,13 @@ impl Movie { let rgb = self.preview.srgb8(view.dn(), view.width); let level = brightest(view.dn(), view.channels as usize, &self.detector); drop(view); - if let Some(longest) = self.auto_exposure { + if self.auto_exposure { + let span = self.frames[k].2; + let longest = self.frames[next % self.frames.len()].2; let exposure = self.renderer.camera().exposure; let wanted = exposure * LEVEL / level.max(1e-6); - let next = wanted.clamp(exposure / self.step, exposure * self.step); + let step = STEP.powf(span); + let next = wanted.clamp(exposure / step, exposure * step); self.renderer .set_exposure(next.clamp(SHORTEST, longest)) .map_err(error)?; diff --git a/crates/resolved/benches/render.rs b/crates/resolved/benches/render.rs index ac25f29..5df1b43 100644 --- a/crates/resolved/benches/render.rs +++ b/crates/resolved/benches/render.rs @@ -7,7 +7,8 @@ //! Landsat 8 (85k triangles) and ICESat-2 (752k triangles). Each runs on the GPU and //! on lavapipe at 1024 x 1024 and 1920 x 1080, at lighting L0; and at L1 at 1920 x 1080 //! if the Earth and Moon maps are in `data/` (`scripts/fetch-earth.sh`, -//! `scripts/fetch-moon.py`), Earth showing July. +//! `scripts/fetch-moon.py`), Earth showing July, with cameras C0, C1 and C2. From C1 +//! the stars are drawn too when `data/stars/stars.bin` is there (`scripts/fetch-stars.py`). //! //! Per-pass GPU times are not here; `resolved render --timings N` reports them. @@ -19,8 +20,8 @@ use resolved::gpu::AdapterChoice; use resolved::nalgebra::{Matrix3, Rotation3, UnitQuaternion, Vector3}; use resolved::radiometry::AU; use resolved::{ - CameraConfig, EarthTexture, Fidelity, LightingTier, LoadOptions, Mesh, MoonTexture, Pose, - Renderer, RendererOptions, Scene, Target, + CameraConfig, CameraTier, EarthTexture, Fidelity, LightingTier, LoadOptions, Mesh, MoonTexture, + Pose, Renderer, RendererOptions, Scene, StarCatalog, StarPhotometry, Target, }; const EARTH_RADIUS: f64 = 6_378_137.0; @@ -30,9 +31,13 @@ fn examples() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("../../examples/models") } +fn data() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data") +} + /// Options for lighting L1 with the maps from `data/`, or None if they are missing. fn l1_options() -> Option { - let data = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data"); + let data = data(); let (earth, moon) = ( data.join("earth/bmng-2004-07.jpg"), data.join("moon/wac-hapke-643.png"), @@ -52,6 +57,18 @@ fn l1_options() -> Option { }) } +/// The star catalog for the example camera's exposure, or None if it is missing. +fn stars() -> Option> { + let path = data().join("stars/stars.bin"); + if !path.is_file() { + eprintln!("skipping stars: run scripts/fetch-stars.py"); + return None; + } + let camera = CameraConfig::example(); + let faint = StarPhotometry::new(&camera).faintest_drawn(camera.exposure); + Some(Arc::new(StarCatalog::load(&path, 0.0, faint).unwrap())) +} + /// The models to bench, skipping NASA models that have not been fetched. fn models() -> Vec<(&'static str, Mesh)> { let mut models = vec![( @@ -125,7 +142,22 @@ fn frames(c: &mut Criterion) { vec![(1024, 1024), (1920, 1080)], )]; if let Some(l1) = l1_options() { - tiers.push(("frame-l1", l1, vec![(1920, 1080)])); + let stars = stars(); + for (tier, camera) in [ + ("frame-l1", CameraTier::C0), + ("frame-l1-c1", CameraTier::C1), + ("frame-l1-c2", CameraTier::C2), + ] { + let options = RendererOptions { + fidelity: Fidelity { + camera, + ..l1.fidelity + }, + stars: (camera >= CameraTier::C1).then(|| stars.clone()).flatten(), + ..l1.clone() + }; + tiers.push((tier, options, vec![(1920, 1080)])); + } } for (tier, base, sizes) in &tiers { for (device, adapter) in [("gpu", AdapterChoice::Gpu), ("cpu", AdapterChoice::Cpu)] { diff --git a/docs/images/l1-c1.png b/docs/images/l1-c1.png new file mode 100644 index 0000000..46f1328 Binary files /dev/null and b/docs/images/l1-c1.png differ diff --git a/docs/images/l1-c2.png b/docs/images/l1-c2.png new file mode 100644 index 0000000..c5239d6 Binary files /dev/null and b/docs/images/l1-c2.png differ diff --git a/docs/optics.md b/docs/optics.md index 7b690b2..02cdd8d 100644 --- a/docs/optics.md +++ b/docs/optics.md @@ -529,6 +529,10 @@ times the dark current (`Detector::prnu`, `dsnu`, `hot_pixel_fraction`, `hot_pixel_multiple`). Because the dark factor scales the dark current, the dark pattern grows with the exposure, as a real one does: at 0.1 ms the example camera's hot pixels make 0.1 electrons, invisible; over 5 s, 5,000. +A camera made for long exposures is calibrated: its hot pixels are found in +dark frames and mapped out. The long-exposure example scenes (the Pleiades +and the GEO telescopes) leave 1 value in a million hot instead +(`hot_pixels = [1e-6, 50]` in a scene file's `[camera]`). The pattern comes from `Detector::pattern_seed`, apart from the noise's seed, so one camera keeps its pattern whatever the frame or noise: the same diff --git a/examples/scenes/geo-telescope-1000km.toml b/examples/scenes/geo-telescope-1000km.toml index fea63d0..20c320b 100644 --- a/examples/scenes/geo-telescope-1000km.toml +++ b/examples/scenes/geo-telescope-1000km.toml @@ -18,6 +18,8 @@ earth_velocity = [-19407.62, -20723.58, -8984.77] focal_length = 1.0 f_number = 4.0 exposure = 0.2 +# Calibrated: hot pixels mapped out, all but 1 value in a million. +hot_pixels = [1e-6, 50] [observer] position = [41083339.41, 9484836.26, 0.00] diff --git a/examples/scenes/geo-telescope-100km-side.toml b/examples/scenes/geo-telescope-100km-side.toml index d65f31b..be70979 100644 --- a/examples/scenes/geo-telescope-100km-side.toml +++ b/examples/scenes/geo-telescope-100km-side.toml @@ -16,6 +16,8 @@ earth_velocity = [8161.41, -26276.53, -11392.27] focal_length = 1.0 f_number = 8.0 exposure = 0.0006 +# Calibrated: hot pixels mapped out, all but 1 value in a million. +hot_pixels = [1e-6, 50] [observer] position = [41083339.41, 9484836.26, 0.00] diff --git a/examples/scenes/geo-telescope-100km.toml b/examples/scenes/geo-telescope-100km.toml index e37186b..66d8a07 100644 --- a/examples/scenes/geo-telescope-100km.toml +++ b/examples/scenes/geo-telescope-100km.toml @@ -17,6 +17,8 @@ earth_velocity = [-19658.75, -20523.36, -8897.97] focal_length = 1.0 f_number = 8.0 exposure = 0.0006 +# Calibrated: hot pixels mapped out, all but 1 value in a million. +hot_pixels = [1e-6, 50] [observer] position = [41083339.41, 9484836.26, 0.00] diff --git a/examples/scenes/stars-pleiades.toml b/examples/scenes/stars-pleiades.toml index 95c08eb..17a0752 100644 --- a/examples/scenes/stars-pleiades.toml +++ b/examples/scenes/stars-pleiades.toml @@ -12,6 +12,8 @@ earth_velocity = [-25760.94, 13707.76, 5943.04] [camera] exposure = 5 +# Calibrated: hot pixels mapped out, all but 1 value in a million. +hot_pixels = [1e-6, 50] [observer] position = [42164000.00, 0.00, 0.00] diff --git a/readme.md b/readme.md index 98d7f7b..48cdb53 100644 --- a/readme.md +++ b/readme.md @@ -32,6 +32,8 @@ step up is visible at a glance. |---|---|---|---|---|---| | L0 / C0 | Lambertian surfaces, Earth's and the Moon's limbs exact, eclipse by Earth and the Moon, no shadows on the target, no light from Earth | Pinhole, absolute radiometry to 12-bit DN, no noise or blur | 0.9–1.3 ms | 5–68 ms | [![L0/C0](docs/images/l0-c0.png)](docs/images/l0-c0.png) | | L1 / C0 | Adds Earth's surface from Blue Marble and the Moon's from the LRO Camera's map, the Moon's measured phase curve, shadows on the target, Earthshine and Moonshine, eclipses seen on Earth and the Moon, the Sun's limb darkening | Pinhole, absolute radiometry to 12-bit DN, no noise or blur | 1.3–2.3 ms | 12–114 ms | [![L1/C0](docs/images/l1-c0.png)](docs/images/l1-c0.png) | +| L1 / C1 | As L1 above, with stars from Hipparcos, Tycho-2 and Gaia | Adds pixels that collect light over their area (the scene rendered 2 x 2 finer, then averaged), lens distortion, a Gaussian lens blur, the Sun, Moon and Earth at their apparent positions, shot, dark and read noise, PRNU, DSNU and hot pixels, blooming | 4.1–5.2 ms | 83–684 ms | [![L1/C1](docs/images/l1-c1.png)](docs/images/l1-c1.png) | +| L1 / C2 | As L1 above, with stars | Adds diffraction (the Airy pattern over each channel's band) or a measured PSF, applied with its wings by FFT | 35–37 ms | 0.61–1.22 s | [![L1/C2](docs/images/l1-c2.png)](docs/images/l1-c2.png) | Times are one 1920 x 1080 RGB frame end to end (`submit` to `wait_view`, with the DN image ready to read), median of criterion runs, over models from 12 to @@ -40,15 +42,21 @@ the DN image ready to read), median of criterion runs, over models from 12 to sensor pass writing the image into memory the CPU reads. `wait`, which copies the 12 MB image into a new `Vec`, takes 1.8 to 2.0 ms on the RTX 3060 at L0 and 2.3 to 3.1 ms at L1, and on lavapipe 7 to 77 ms and 14 to 144 ms. L1's -setup also loads the Earth and Moon maps, about 1 s, once. lavapipe ran on the -Intel Core Ultra 9 285K's 24 cores. Run to run, the RTX 3060's times vary by -about 5 %, and lavapipe's by up to 15 % between sessions. The samples are the -Landsat 8 example scene (`examples/scenes/landsat8.toml`). `cargo bench -p -resolved` reproduces the numbers (L1 when the maps are in `data/`), and -`resolved render --timings N` shows the per-pass split. +setup also loads the Earth and Moon maps, about 1 s, once. In the sample scene +the GPU's work is 3.8 ms at C1, of which 1.3 ms is drawing Earth and the Moon +at 2 x 2 and 1.0 ms the blur, and 33 ms at C2, of which 30 ms is the FFT +convolution on a 4096 x 4096 grid. The C2 sample uses the example camera's +Gaussian PSF, and looks like C1's: at f/4 the Airy core is smaller than a +pixel, and its wings show around the Sun (`examples/scenes/sun-glare.toml`). +lavapipe ran on the Intel Core Ultra 9 285K's 24 cores. Run to run, the RTX +3060's times vary by about 5 %, and lavapipe's by up to 15 % between sessions. +The samples are the Landsat 8 example scene (`examples/scenes/landsat8.toml`), +the preview (not `--pretty`) at half size, with the noise of seed 0. `cargo +bench -p resolved` reproduces the numbers (L1 when the maps are in `data/`, +and stars from C1 when the catalog is), and `resolved render --timings N` +shows the per-pass split. -Planned: C1 and C2 add pixel-area sampling, lens distortion, blur and detector -noise; L2 adds measured materials and the atmosphere; L3 is path traced. +Planned: L2 adds measured materials and the atmosphere; L3 is path traced. ## Usage @@ -137,23 +145,27 @@ cargo run --release -p resolved-cli -- render \ --out frame.png --preview preview.png --timings 50 ``` -`scripts/make-gallery.sh` renders every example scene at L0 and L1 into -`gallery/` with `--pretty`, skipping any whose model or data is missing, and -`scripts/serve-gallery.py` serves the result as a web page on port 8000 of -every interface, each scene captioned from its file. +`scripts/make-gallery.sh` renders every example scene at L0 and L1, with +camera C1, into `gallery/` with `--pretty`, skipping any whose model or data +is missing, and `scripts/serve-gallery.py` serves the result as a web page on +port 8000 of every interface, each scene captioned from its file. -`crates/resolved-showcase` makes a 30-second movie at lighting L1: an inspector -holding station behind a Landsat 8 model 705 km up, which astrokit propagates -with J2. Time runs fast over the daylit Earth, slows as the Moon rises over the -limb ahead, and slows almost to real time as the spacecraft passes into Earth's -shadow. The camera drifts on a small natural relative orbit around its station, -and adjusts its exposure frame by frame as a video camera does (up to one -frame's time), through dawn and dusk. `--out movie.mp4` encodes it with ffmpeg, -rendering each 1920 x 1080 frame in about 4 ms on the RTX 3060. `--serve +`crates/resolved-showcase` makes a 30-second movie at lighting L1 and camera +C1: an inspector holding station behind a Landsat 8 model 705 km up, which +astrokit propagates with J2. Time runs fast over the daylit Earth, slows as +the Moon rises over the limb ahead, and slows almost to real time as the +spacecraft passes into Earth's shadow. The camera drifts on a small natural +relative orbit around its station, and adjusts its exposure frame by frame, up +to the flight time between frames, by at most 6 times per second of flight. It +meters the first frame before taking it. In Earth's shadow at the end, +exposures reach about 2 s, and show the target lit by the Moon and the stars +above the limb. `--out movie.mp4` encodes it with ffmpeg, rendering and tone +mapping each 1920 x 1080 frame in about 9 ms on the RTX 3060. `--serve 0.0.0.0:8010` renders it live, in step with the clock, and streams it as MJPEG -at 60 frames per second (`--fps`; it keeps up to 120, rendering each frame in -under 5 ms) to the gallery page's Movie tab, which also plays and offers the -recorded file (`make-gallery.sh` writes `gallery/movie.mp4`): +at 60 frames per second (`--fps`; at 120 it skips up to 16 frames in 5 s), +skipping about 9 more where the flight starts over, while it meters, to the +gallery page's Movie tab, which also plays and offers the recorded file +(`make-gallery.sh` writes `gallery/movie.mp4`): ```sh cargo run --release -p resolved-showcase -- --serve 0.0.0.0:8010 @@ -199,23 +211,21 @@ cargo run --release -p resolved-showcase -- --serve 0.0.0.0:8010 ## Status -Lighting L0 and L1 and camera C0 are in: the library renders Earth, Moon, Sun -and targets to DN through `Renderer`, and the CLI renders scene files. Lighting -L1 draws Earth's surface from Blue Marble and the Moon's from the LRO Camera's -map, shadows on the target, light from Earth and the Moon on it, eclipses on -Earth and the Moon, the Sun's limb darkening, and stars from Hipparcos, -Tycho-2 and Gaia, which camera C1 shows. Camera C1 is under way: so far, +Lighting L0 and L1 and cameras C0, C1 and C2 are in: the library renders +Earth, Moon, Sun and targets to DN through `Renderer`, and the CLI renders +scene files. Lighting L1 draws Earth's surface from Blue Marble and the Moon's +from the LRO Camera's map, shadows on the target, light from Earth and the +Moon on it, eclipses on Earth and the Moon, the Sun's limb darkening, and +stars from Hipparcos, Tycho-2 and Gaia, which camera C1 shows. Camera C1 adds pixels that collect light over their area, lens distortion (Brown-Conrady), lens blur (a Gaussian PSF) applied to the scene rendered 2 x 2 finer than the -pixels, the Sun, Moon and Earth at their apparent positions (light time -and aberration), detector noise (shot, dark current and read noise, seeded -and reproducible), the detector's fixed pattern (PRNU, DSNU and hot pixels), -and blooming along columns or into neighbors. Camera C2 adds diffraction -(the Airy pattern, averaged over each channel's band) and measured PSFs, -applied with their wings by FFT. Planned, in order: +pixels, the Sun, Moon and Earth at their apparent positions (light time and +aberration), detector noise (shot, dark current and read noise, seeded and +reproducible), the detector's fixed pattern (PRNU, DSNU and hot pixels), and +blooming along columns or into neighbors. Camera C2 adds diffraction (the Airy +pattern, averaged over each channel's band) and measured PSFs, applied with +their wings by FFT. Planned, in order: -- Camera: the C1 and C2 checkpoint, their rows in the table above with - sample images. - Lighting: BRDF and spectral materials, atmospheric scattering, clouds, lunar terrain. - Portable ray tracing, path tracing, and label outputs for ML. diff --git a/scripts/make-example-scenes.py b/scripts/make-example-scenes.py index 52579d0..c7f02f3 100755 --- a/scripts/make-example-scenes.py +++ b/scripts/make-example-scenes.py @@ -531,6 +531,12 @@ attitude = {fmt(quaternion(attitude), 9)} """ +# For the long-exposure cameras, which leave the example camera's 1 in 10,000 hot. +CALIBRATED = """# Calibrated: hot pixels mapped out, all but 1 value in a million. +hot_pixels = [1e-6, 50] +""" + + def stars_pleiades(): # The Pleiades, right ascension 56.75 and declination 24.12 degrees, from # geostationary orbit, with the Sun behind the camera. @@ -553,7 +559,7 @@ earth_velocity = {fmt(earth_velocity(sun))} [camera] exposure = 5 - +{CALIBRATED} [observer] position = {fmt(camera)} look_at = {fmt([camera[i] + 1e9 * look[i] for i in range(3)])} @@ -609,7 +615,7 @@ earth_velocity = {fmt(earth_velocity(sun))} focal_length = 1.0 f_number = {f_number:.1f} exposure = {exposure} - +{CALIBRATED} [observer] position = {fmt(camera)} look_at = {fmt(spot)} diff --git a/scripts/make-gallery.sh b/scripts/make-gallery.sh index 32f068b..f8b5388 100755 --- a/scripts/make-gallery.sh +++ b/scripts/make-gallery.sh @@ -1,13 +1,14 @@ #!/bin/sh -# Renders every example scene at lighting L0 and L1 into gallery/ (gitignored), as -# -L0.png and -L1.png previews made with --pretty: white balanced, with a -# tone curve, from exposures that keep the raw signal short of saturation. A scene whose -# model, maps or kernels are missing is skipped with the reason. Then the showcase -# movie, gallery/movie.mp4 (resolved-movie, which needs ffmpeg). View the result with -# scripts/serve-gallery.py. +# Renders every example scene at lighting L0 and L1, with camera C1, into gallery/ +# (gitignored), as -L0.png and -L1.png previews made with --pretty: white +# balanced, with a tone curve, from exposures that keep the raw signal short of +# saturation. A scene whose model, maps or kernels are missing is skipped with the +# reason. Then the showcase movie, gallery/movie.mp4 (resolved-movie, which needs +# ffmpeg). View the result with scripts/serve-gallery.py. # -# Needs: scripts/fetch-models.sh for the NASA models, scripts/fetch-earth.sh and -# scripts/fetch-moon.py for L1, and astrokit's kernels for scenes given by epoch. +# Needs: scripts/fetch-models.sh for the NASA models, scripts/fetch-earth.sh, +# scripts/fetch-moon.py and scripts/fetch-stars.py for L1, and astrokit's kernels for +# scenes given by epoch. set -e cd "$(dirname "$0")/.." @@ -22,7 +23,7 @@ for scene in examples/scenes/*.toml; do for tier in L0 L1; do out="gallery/$name-$tier.png" if error=$(./target/release/resolved render --scene "$scene" --lighting "$tier" \ - --preview "$out" --pretty 2>&1); then + --camera C1 --preview "$out" --pretty 2>&1); then echo "$out" made=$((made + 1)) else diff --git a/scripts/serve-gallery.py b/scripts/serve-gallery.py index 86d5f29..31bf746 100755 --- a/scripts/serve-gallery.py +++ b/scripts/serve-gallery.py @@ -4,9 +4,9 @@ looking at the example renders from another machine on the network. python3 scripts/serve-gallery.py [--port 8000] [--live-port 8010] -Stills: each example scene gets a card with its L0 and L1 renders side by side, -captioned from the comment at the top of its file in examples/scenes/. Click an image -to see it full size; arrow keys step through them and Escape closes. +Stills: each example scene gets a card with its L0 and L1 renders side by side, both +with camera C1, captioned from the comment at the top of its file in examples/scenes/. +Click an image to see it full size; arrow keys step through them and Escape closes. Movie: gallery/movie.mp4 (from make-gallery.sh or resolved-movie --out) to play or download, and the live stream from `resolved-movie --serve 0.0.0.0:8010` on this @@ -30,6 +30,8 @@ ROOT = Path(__file__).resolve().parent.parent GALLERY = ROOT / "gallery" SCENES = ROOT / "examples" / "scenes" TIERS = ["L0", "L1"] +# The camera tier make-gallery.sh renders with. +CAMERA = "C1" STYLE = """ :root { @@ -173,8 +175,8 @@ def movie_section(host, live_port): return f"""

The movie

An inspector holds station 120 m behind and above a Landsat 8 model 705 km up; the target is propagated by astrokit with J2 and everything is rendered at -lighting L1. The camera drifts on a small natural relative orbit around its station and -sets its exposure frame by frame. Time runs fast over the daylit Earth, slows as the +lighting L1 and camera C1. The camera drifts on a small natural relative orbit around +its station and sets its exposure frame by frame. Time runs fast over the daylit Earth, slows as the Moon rises over the limb ahead, and slows almost to real time as the spacecraft passes into Earth's shadow.

{recorded}

Live

@@ -199,16 +201,16 @@ def page(host, live_port): if image.is_file(): count += 1 stamp = int(image.stat().st_mtime) - alt = f"{name}, lighting {tier}" + alt = f"{name}, lighting {tier}, camera {CAMERA}" figures.append( f'
' - f"
{tier} lighting
" + f"
{tier} lighting, camera {CAMERA}
" ) else: figures.append( f'
no {tier} render
' - f"
{tier} lighting
" + f"
{tier} lighting, camera {CAMERA}
" ) first = html.escape(paragraphs[0]) if paragraphs else "" more = ""