From e77d68d0a64838566fad9a97ee860507c279ac7b Mon Sep 17 00:00:00 2001 From: Connor Johnstone Date: Wed, 14 Oct 2026 20:12:49 -0400 Subject: [PATCH] Added earth surface texture --- .gitignore | 1 + Cargo.lock | 2 + crates/resolved-cli/src/main.rs | 155 ++++++++---- crates/resolved-cli/src/scene_file.rs | 51 ++++ crates/resolved-cli/tests/cli.rs | 54 +++++ crates/resolved/Cargo.toml | 2 + crates/resolved/src/bodies.rs | 19 +- crates/resolved/src/earth.rs | 192 +++++++++++++++ crates/resolved/src/error.rs | 8 + crates/resolved/src/fidelity.rs | 100 ++++++++ crates/resolved/src/gpu/bodies.rs | 137 ++++++++++- crates/resolved/src/lib.rs | 4 + crates/resolved/src/model.rs | 16 +- crates/resolved/src/render.rs | 49 +++- crates/resolved/src/shaders/bodies.wgsl | 51 +++- crates/resolved/tests/earth_texture.rs | 308 ++++++++++++++++++++++++ docs/images/l0-c0.png | Bin 51809 -> 53062 bytes docs/optics.md | 61 ++++- examples/scenes/ace.toml | 9 +- examples/scenes/boxwing-epoch.toml | 2 + examples/scenes/icesat2.toml | 9 +- examples/scenes/landsat8.toml | 9 +- examples/scenes/ostm.toml | 9 +- examples/scenes/ssl1300.toml | 9 +- readme.md | 19 +- scripts/fetch-earth.sh | 53 ++++ scripts/make-example-scenes.py | 48 +++- 27 files changed, 1284 insertions(+), 93 deletions(-) create mode 100644 crates/resolved/src/earth.rs create mode 100644 crates/resolved/src/fidelity.rs create mode 100644 crates/resolved/tests/earth_texture.rs create mode 100755 scripts/fetch-earth.sh diff --git a/.gitignore b/.gitignore index dd4ff23..123d8d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target /examples/models/nasa/ +/data/ diff --git a/Cargo.lock b/Cargo.lock index db87dbd..1887508 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -696,6 +696,7 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ + "bytemuck", "cfg-if", "crunchy", "num-traits", @@ -1555,6 +1556,7 @@ dependencies = [ "bytemuck", "criterion", "gltf", + "half", "hifitime", "image", "log", diff --git a/crates/resolved-cli/src/main.rs b/crates/resolved-cli/src/main.rs index 5912424..ff56f96 100644 --- a/crates/resolved-cli/src/main.rs +++ b/crates/resolved-cli/src/main.rs @@ -4,11 +4,14 @@ mod scene_file; use std::path::{Path, PathBuf}; use std::process::ExitCode; +use std::sync::Arc; use std::time::Instant; -use clap::{Parser, Subcommand}; +use clap::{Args, Parser, Subcommand}; use resolved::gpu::AdapterChoice; -use resolved::{Frame, Renderer, RendererOptions}; +use resolved::{ + CameraTier, EarthTexture, Fidelity, Frame, LightingTier, Renderer, RendererOptions, +}; use crate::scene_file::SceneFile; @@ -25,32 +28,46 @@ struct Cli { #[derive(Subcommand)] enum Command { /// Render a scene file. - Render { - /// The scene, as TOML (see examples/scenes/). - #[arg(long)] - scene: PathBuf, - /// Raw DN as a 16-bit PNG (RGB for three channels, gray for one), shifted up to - /// fill the 16-bit range: DN = value >> (16 - ADC bits), so 12-bit DN are x 16. - #[arg(long)] - out: Option, - /// An 8-bit sRGB PNG for viewing: bias subtracted, saturation as white. - #[arg(long)] - preview: Option, - /// Device: auto, gpu, cpu, or part of an adapter's name. - #[arg(long, default_value = "auto")] - adapter: String, - /// Render this many frames and report setup and per-frame times. - #[arg(long, value_name = "FRAMES")] - timings: Option, - /// Directory of astrokit's kernels, for scenes given by epoch. Without it: - /// ASTROKIT_KERNELS, then ./data/kernels, then the sibling astrokit checkout. - #[arg(long, value_name = "DIR")] - kernels: Option, - }, + Render(RenderArgs), /// List the adapters this machine offers. Adapters, } +#[derive(Args)] +struct RenderArgs { + /// The scene, as TOML (see examples/scenes/). + #[arg(long)] + scene: PathBuf, + /// Raw DN as a 16-bit PNG (RGB for three channels, gray for one), shifted up to + /// fill the 16-bit range: DN = value >> (16 - ADC bits), so 12-bit DN are x 16. + #[arg(long)] + out: Option, + /// An 8-bit sRGB PNG for viewing: bias subtracted, saturation as white. + #[arg(long)] + preview: Option, + /// Lighting fidelity tier: L0, or L1 (adds Earth's surface; needs Blue Marble data). + #[arg(long, default_value = "L0")] + lighting: LightingTier, + /// Camera fidelity tier: C0. + #[arg(long, default_value = "C0")] + camera: CameraTier, + /// Device: auto, gpu, cpu, or part of an adapter's name. + #[arg(long, default_value = "auto")] + adapter: String, + /// Render this many frames and report setup and per-frame times. + #[arg(long, value_name = "FRAMES")] + timings: Option, + /// Directory of data the tiers need (earth/bmng-2004-MM.jpg from + /// scripts/fetch-earth.sh). Without it: RESOLVED_DATA, then ./data, then the + /// repository's data/. + #[arg(long, value_name = "DIR")] + data: Option, + /// Directory of astrokit's kernels, for scenes given by epoch. Without it: + /// ASTROKIT_KERNELS, then ./data/kernels, then the sibling astrokit checkout. + #[arg(long, value_name = "DIR")] + kernels: Option, +} + fn main() -> ExitCode { let result = match Cli::parse().command { Command::Adapters => { @@ -62,21 +79,7 @@ fn main() -> ExitCode { } Ok(()) } - Command::Render { - scene, - out, - preview, - adapter, - timings, - kernels, - } => render( - &scene, - out.as_deref(), - preview.as_deref(), - &adapter, - timings, - kernels.as_deref(), - ), + Command::Render(args) => render(&args), }; match result { Ok(()) => ExitCode::SUCCESS, @@ -87,37 +90,79 @@ fn main() -> ExitCode { } } -fn render( - path: &Path, - out: Option<&Path>, - preview: Option<&Path>, - adapter: &str, - timings: Option, - kernels: Option<&Path>, -) -> Result<(), String> { +/// Where to look for data: `explicit` alone if given, else `RESOLVED_DATA`, `./data` +/// and the repository's `data/`. +fn data_directories(explicit: Option<&Path>) -> Vec { + if let Some(dir) = explicit { + return vec![dir.to_owned()]; + } + let repository = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data"); + std::env::var_os("RESOLVED_DATA") + .map(PathBuf::from) + .into_iter() + .chain([PathBuf::from("data"), repository]) + .collect() +} + +/// The Earth image for `month` (1-12), from the first data directory that has it. +fn earth_image(data: Option<&Path>, month: u32) -> Result { + let name = format!("earth/bmng-2004-{month:02}.jpg"); + let tried = data_directories(data); + tried.iter().map(|dir| dir.join(&name)).find(|p| p.is_file()).ok_or_else(|| { + let tried: Vec<_> = tried.iter().map(|d| d.display().to_string()).collect(); + format!( + "lighting L1 needs {name} (run scripts/fetch-earth.sh, or pass --data DIR); looked in {}", + tried.join(", ") + ) + }) +} + +fn render(args: &RenderArgs) -> Result<(), String> { + let (out, preview, timings) = (args.out.as_deref(), args.preview.as_deref(), args.timings); if out.is_none() && preview.is_none() && timings.is_none() { return Err("nothing to do: give --out, --preview or --timings".into()); } + let path = args.scene.as_path(); let file = SceneFile::read(path)?; let camera = file.camera(); + + // What the tiers need, gathered before the renderer exists so it can check them. + let start = Instant::now(); + let earth_texture = if args.lighting >= LightingTier::L1 { + let month = file.earth_month()?.ok_or( + "lighting L1 needs the month for Earth's surface: give the scene an epoch, or [appearance] earth_month", + )?; + let image = earth_image(args.data.as_deref(), month)?; + Some(Arc::new( + EarthTexture::load(&image).map_err(|e| e.to_string())?, + )) + } else { + None + }; + let texturing = start.elapsed(); + let options = RendererOptions { - adapter: match adapter { + adapter: match args.adapter.as_str() { "auto" => AdapterChoice::Auto, "gpu" => AdapterChoice::Gpu, "cpu" => AdapterChoice::Cpu, name => AdapterChoice::Named(name.to_owned()), }, + fidelity: Fidelity { + lighting: args.lighting, + camera: args.camera, + }, + earth_texture, appearance: file.appearance(), read_radiance: false, profile: timings.is_some(), }; - let start = Instant::now(); let mut renderer = Renderer::new(camera, options).map_err(|e| e.to_string())?; let setup = start.elapsed(); let start = Instant::now(); let dir = path.parent().unwrap_or(Path::new(".")); - let scene = file.scene(&mut renderer, dir, kernels)?; + let scene = file.scene(&mut renderer, dir, args.kernels.as_deref())?; let observer = file.observer()?; let loading = start.elapsed(); @@ -138,7 +183,15 @@ fn render( if timings.is_some() { println!("adapter {}", renderer.adapter_info().name); - println!("setup {:.1} ms", setup.as_secs_f64() * 1e3); + println!("tiers {} {}", args.lighting, args.camera); + println!( + "setup {:.1} ms (includes the Earth texture's upload)", + setup.as_secs_f64() * 1e3 + ); + println!( + "earth {:.1} ms (texture decode)", + texturing.as_secs_f64() * 1e3 + ); println!( "scene {:.1} ms (models and ephemeris)", loading.as_secs_f64() * 1e3 diff --git a/crates/resolved-cli/src/scene_file.rs b/crates/resolved-cli/src/scene_file.rs index a9c6679..7086dcb 100644 --- a/crates/resolved-cli/src/scene_file.rs +++ b/crates/resolved-cli/src/scene_file.rs @@ -75,6 +75,9 @@ pub struct ObserverFile { pub struct AppearanceFile { pub earth_albedo: Option, pub moon_albedo: Option, + /// Which month (1-12) Earth's surface shows at lighting L1, for scenes without an + /// epoch; with one, the epoch's month is the default. + pub earth_month: Option, } /// One spacecraft. @@ -154,6 +157,32 @@ impl SceneFile { a } + /// The month (1-12) for Earth's surface: `earth_month`, else the epoch's month. + pub fn earth_month(&self) -> Result> { + let month = match self.appearance.earth_month { + Some(m) => Some(m), + None => self.epoch_month()?, + }; + match month { + Some(m) if !(1..=12).contains(&m) => Err(format!("earth_month {m} is not 1-12")), + m => Ok(m), + } + } + + #[cfg(feature = "astrokit")] + fn epoch_month(&self) -> Result> { + let Some(epoch) = &self.epoch else { + return Ok(None); + }; + let epoch: hifitime::Epoch = epoch.parse().map_err(|e| format!("epoch {epoch:?}: {e}"))?; + Ok(Some(epoch.to_gregorian_utc().1 as u32)) + } + + #[cfg(not(feature = "astrokit"))] + fn epoch_month(&self) -> Result> { + Ok(None) + } + /// The observer's pose. pub fn observer(&self) -> Result { let o = &self.observer; @@ -329,6 +358,28 @@ mod tests { assert_eq!(camera.detector.adc_bits, 10); } + #[test] + fn earth_month_comes_from_the_setting() { + let with = |extra: &str| -> SceneFile { + toml::from_str(&format!("{MINIMAL}\n[appearance]\n{extra}\n")).unwrap() + }; + assert_eq!(with("earth_month = 3").earth_month().unwrap(), Some(3)); + assert_eq!( + with("").earth_month().unwrap(), + None, + "no month and no epoch" + ); + assert!(with("earth_month = 13").earth_month().is_err()); + } + + #[cfg(feature = "astrokit")] + #[test] + fn earth_month_defaults_to_the_epoch() { + let file: SceneFile = + toml::from_str(&format!("epoch = \"2026-10-14T12:00:00 UTC\"\n{MINIMAL}")).unwrap(); + assert_eq!(file.earth_month().unwrap(), Some(10)); + } + #[test] fn mistakes_are_reported() { // A misspelled field. diff --git a/crates/resolved-cli/tests/cli.rs b/crates/resolved-cli/tests/cli.rs index 9fb66d1..88e0843 100644 --- a/crates/resolved-cli/tests/cli.rs +++ b/crates/resolved-cli/tests/cli.rs @@ -123,6 +123,60 @@ fn missing_kernels_say_where_they_looked() { ); } +#[test] +fn lighting_l1_says_what_it_is_missing() { + // boxwing.toml has no epoch and no earth_month: L1 cannot pick a month. + let no_month = resolved(&[ + "render", + "--scene", + "examples/scenes/boxwing.toml", + "--lighting", + "L1", + "--preview", + "x.png", + ]); + assert!(!no_month.status.success()); + let stderr = String::from_utf8_lossy(&no_month.stderr); + assert!(stderr.contains("earth_month"), "{stderr}"); + + // With a month but no data: the error names the file and the fetch script. + let scene = out_dir().join("month.toml"); + let models = repo().join("examples/models").canonicalize().unwrap(); + let text = std::fs::read_to_string(repo().join("examples/scenes/boxwing.toml")) + .unwrap() + .replace("../models", models.to_str().unwrap()) + + "\n[appearance]\nearth_month = 7\n"; + std::fs::write(&scene, text).unwrap(); + let no_data = resolved(&[ + "render", + "--scene", + scene.to_str().unwrap(), + "--lighting", + "L1", + "--data", + "no/such/dir", + "--preview", + "x.png", + ]); + assert!(!no_data.status.success()); + let stderr = String::from_utf8_lossy(&no_data.stderr); + assert!( + stderr.contains("bmng-2004-07.jpg") && stderr.contains("fetch-earth.sh"), + "{stderr}" + ); + + let unknown = resolved(&[ + "render", + "--scene", + "examples/scenes/boxwing.toml", + "--lighting", + "L7", + "--preview", + "x.png", + ]); + assert!(!unknown.status.success()); +} + #[test] fn adapters_lists_something() { let result = resolved(&["adapters"]); diff --git a/crates/resolved/Cargo.toml b/crates/resolved/Cargo.toml index 5bd1492..93e5545 100644 --- a/crates/resolved/Cargo.toml +++ b/crates/resolved/Cargo.toml @@ -30,6 +30,8 @@ pollster = "1.0" bytemuck = { version = "1.0", features = ["derive"] } log = "0.4" gltf = "1.4" +image = { version = "0.25", default-features = false, features = ["png", "jpeg"] } +half = { version = "2.7", features = ["bytemuck"] } stl_io = "0.11" [dev-dependencies] diff --git a/crates/resolved/src/bodies.rs b/crates/resolved/src/bodies.rs index 4c6be67..fc53bf9 100644 --- a/crates/resolved/src/bodies.rs +++ b/crates/resolved/src/bodies.rs @@ -17,8 +17,12 @@ pub const MOON_RADIUS: f64 = 1_737_400.0; /// and better reflectance models in later milestones. #[derive(Clone, Debug, PartialEq)] pub struct Appearance { - /// Earth's reflectance. + /// Earth's reflectance, used when no Earth texture is set. pub earth_albedo: Spectrum, + /// Added to an Earth texture's reflectance: a stand-in for the light Earth's + /// atmosphere scatters toward the camera, until the atmosphere is modeled (M4). + /// Not used with `earth_albedo`, which already includes the atmosphere's effect. + pub earth_haze: Spectrum, /// The Moon's reflectance. pub moon_albedo: Spectrum, } @@ -42,11 +46,24 @@ impl Default for Appearance { Self { earth_albedo: Spectrum::sampled(nm.map(|x| x * 1e-9).to_vec(), earth.to_vec()) .expect("fixed samples"), + earth_haze: rayleigh_haze(), moon_albedo: Spectrum::constant(0.12), } } } +/// Reflectance shaped like Rayleigh scattering, falling as 1/lambda^4: 0.07 at 450 nm, +/// 0.017 at 650 nm. Sampled every 10 nm from 300 to 1100 nm, zero outside. +/// +/// The size is a round figure of the right order: clear air's Rayleigh optical depth is +/// about 0.2 at 450 nm, and the light it scatters toward a camera above it amounts to a +/// reflectance of a few hundredths to a tenth, depending on the angles. +fn rayleigh_haze() -> Spectrum { + let nm: Vec = (30..=110).map(|i| i as f64 * 10.0).collect(); + let value = nm.iter().map(|l| 0.07 * (450.0 / l).powi(4)).collect(); + Spectrum::sampled(nm.iter().map(|l| l * 1e-9).collect(), value).expect("increasing samples") +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/resolved/src/earth.rs b/crates/resolved/src/earth.rs new file mode 100644 index 0000000..a93227f --- /dev/null +++ b/crates/resolved/src/earth.rs @@ -0,0 +1,192 @@ +//! Earth's surface as an image: albedo by latitude and longitude. +//! +//! The image is equirectangular: longitude runs from -180 degrees at the left edge to +//! +180 at the right, latitude from +90 at the top to -90 at the bottom, in Earth-fixed +//! (ITRF) axes with geodetic latitude. Pixel values are sRGB-encoded red, green and +//! blue reflectance, as in NASA's Blue Marble (`scripts/fetch-earth.sh`). + +use std::path::Path; + +use half::f16; + +use crate::Error; + +/// An equirectangular image of Earth's surface reflectance. +#[derive(Clone)] +pub struct EarthTexture { + width: u32, + height: u32, + /// RGBA8, sRGB-encoded; alpha unused. + pixels: Vec, +} + +impl std::fmt::Debug for EarthTexture { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EarthTexture") + .field("width", &self.width) + .field("height", &self.height) + .finish_non_exhaustive() + } +} + +impl EarthTexture { + /// Loads an image file (JPEG or PNG). + pub fn load(path: &Path) -> Result { + let image = + image::open(path).map_err(|e| Error::Texture(format!("{}: {e}", path.display())))?; + let rgba = image.to_rgba8(); + Self::from_rgba8(rgba.width(), rgba.height(), rgba.into_raw()) + } + + /// An image from sRGB-encoded RGBA8 pixels, rows from the top. + pub fn from_rgba8(width: u32, height: u32, pixels: Vec) -> Result { + if width == 0 || height == 0 || pixels.len() != 4 * width as usize * height as usize { + return Err(Error::Texture(format!( + "{width} x {height} needs {} bytes of RGBA, got {}", + 4 * width as usize * height as usize, + pixels.len() + ))); + } + Ok(Self { + width, + height, + pixels, + }) + } + + /// Width in pixels. + pub fn width(&self) -> u32 { + self.width + } + + /// Height in pixels. + pub fn height(&self) -> u32 { + self.height + } + + /// Every mip level from full size down to 1 x 1, each half the size of the one + /// before (rounded down, at least 1), as linear reflectance in RGBA f16. + /// + /// Each level averages 2 x 2 blocks of the one above, in linear values, so a far view + /// of Earth averages reflectance. The GPU stores and filters these linear values too: + /// sRGB textures would let the hardware decode, but lavapipe's sRGB decode is off by + /// up to 3 % for dark values like ocean, and filtering encoded values instead darkens + /// the blend between contrasting texels (a coastline) by up to half. + pub(crate) fn mip_chain(&self) -> Vec<(u32, u32, Vec)> { + let decode: Vec = (0..=255u8) + .map(|v| srgb_to_linear(v as f32 / 255.0)) + .collect(); + let one = f16::from_f32(1.0); + let mut first = vec![one; self.pixels.len()]; + parallel_rows(&mut first, 4 * self.width as usize, |y, row| { + let src = &self.pixels[y * row.len()..(y + 1) * row.len()]; + for (dst, src) in row.chunks_exact_mut(4).zip(src.chunks_exact(4)) { + for c in 0..3 { + dst[c] = f16::from_f32(decode[src[c] as usize]); + } + } + }); + let mut levels = vec![(self.width, self.height, first)]; + loop { + let (w, h, above) = levels.last().expect("at least one level"); + let (w, h) = (*w, *h); + if w == 1 && h == 1 { + break; + } + let (nw, nh) = ((w / 2).max(1), (h / 2).max(1)); + let mut below = vec![one; 4 * nw as usize * nh as usize]; + parallel_rows(&mut below, 4 * nw as usize, |y, row| { + let y = y as u32; + for x in 0..nw { + for c in 0..3 { + let mut sum = 0.0; + for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] { + let sx = (2 * x + dx).min(w - 1); + let sy = (2 * y + dy).min(h - 1); + sum += above[4 * (sy * w + sx) as usize + c].to_f32(); + } + row[4 * x as usize + c] = f16::from_f32(sum / 4.0); + } + } + }); + levels.push((nw, nh, below)); + } + levels + } +} + +/// Runs `f(row_index, row)` over the rows of `data` (rows of `row_len` values) on all +/// cores. +fn parallel_rows(data: &mut [T], row_len: usize, f: impl Fn(usize, &mut [T]) + Sync) { + let rows = data.len() / row_len; + let threads = std::thread::available_parallelism() + .map_or(1, |n| n.get()) + .min(rows.max(1)); + let per_thread = rows.div_ceil(threads); + std::thread::scope(|scope| { + for (t, block) in data.chunks_mut(per_thread * row_len).enumerate() { + let f = &f; + scope.spawn(move || { + for (i, row) in block.chunks_mut(row_len).enumerate() { + f(t * per_thread + i, row); + } + }); + } + }); +} + +fn srgb_to_linear(v: f32) -> f32 { + if v <= 0.040_45 { + v / 12.92 + } else { + ((v + 0.055) / 1.055).powf(2.4) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn mip_chain_averages_in_linear_light() { + // A 2 x 2 checker of black and white averages to 50 % reflectance, not the 22 % + // that averaging encoded values (sRGB 128) would give. + let px = |v: u8| [v, v, v, 255]; + let pixels = [px(0), px(255), px(255), px(0)].concat(); + let texture = EarthTexture::from_rgba8(2, 2, pixels).unwrap(); + let chain = texture.mip_chain(); + assert_eq!(chain.len(), 2); + assert_eq!((chain[1].0, chain[1].1), (1, 1)); + assert_eq!( + chain[1].2[..3] + .iter() + .map(|v| v.to_f32()) + .collect::>(), + [0.5; 3] + ); + assert_eq!(chain[0].2[4].to_f32(), 1.0, "sRGB 255 is reflectance 1"); + } + + #[test] + fn mip_chain_handles_non_square_sizes() { + let texture = EarthTexture::from_rgba8(8, 4, vec![100; 8 * 4 * 4]).unwrap(); + let sizes: Vec<_> = texture + .mip_chain() + .iter() + .map(|(w, h, _)| (*w, *h)) + .collect(); + assert_eq!(sizes, [(8, 4), (4, 2), (2, 1), (1, 1)]); + // A uniform image stays uniform at every level. + let first = texture.mip_chain()[0].2[0]; + assert!(texture + .mip_chain() + .iter() + .all(|(_, _, p)| p.chunks(4).all(|t| t[..3] == [first; 3]))); + } + + #[test] + fn wrong_sizes_are_rejected() { + assert!(EarthTexture::from_rgba8(4, 2, vec![0; 31]).is_err()); + assert!(EarthTexture::from_rgba8(0, 2, vec![]).is_err()); + } +} diff --git a/crates/resolved/src/error.rs b/crates/resolved/src/error.rs index 7ee3c11..e2a7e4f 100644 --- a/crates/resolved/src/error.rs +++ b/crates/resolved/src/error.rs @@ -21,10 +21,18 @@ pub enum Error { #[error("ephemeris: {0}")] Ephemeris(String), + /// An image that could not be read or has the wrong size. + #[error("texture: {0}")] + Texture(String), + /// A model file that could not be read or describes no usable geometry. #[error("model: {0}")] Model(String), + /// The chosen fidelity tier needs an input that was not supplied. + #[error("missing input: {0}")] + MissingInput(String), + /// A request this version cannot carry out yet. #[error("unsupported: {0}")] Unsupported(String), diff --git a/crates/resolved/src/fidelity.rs b/crates/resolved/src/fidelity.rs new file mode 100644 index 0000000..1d16300 --- /dev/null +++ b/crates/resolved/src/fidelity.rs @@ -0,0 +1,100 @@ +//! Fidelity tiers: how much of the physics a frame models, chosen separately for +//! lighting and for the camera. +//! +//! A tier is a fixed bundle of features, so a tier name says exactly what an image +//! includes, and each tier's cost is measured as a whole (the readme's performance +//! table). A tier that needs data, such as L1's Earth image, makes +//! [`Renderer::new`](crate::Renderer::new) fail if that data is not supplied. + +use std::fmt; +use std::str::FromStr; + +/// Lighting tiers, from cheapest to most complete. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] +pub enum LightingTier { + /// Lambertian surfaces with one reflectance per body, exact Earth and Moon limbs, + /// eclipses of the target by Earth and the Moon. No shadows on the target, no light + /// from Earth or the Moon onto it. + #[default] + L0, + /// L0, plus Earth's surface from an image (`RendererOptions::earth_texture`, NASA's + /// Blue Marble) with a stand-in for the atmosphere's blue. The rest of M2 (shadows, + /// Earthshine, the Moon's albedo map) joins this tier as it lands. + L1, +} + +/// Camera tiers, from cheapest to most complete. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] +pub enum CameraTier { + /// A pinhole camera with absolute radiometry to DN: no distortion, blur or noise. + #[default] + C0, +} + +/// The lighting and camera tiers a renderer draws at. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub struct Fidelity { + /// How light reaches and leaves surfaces. + pub lighting: LightingTier, + /// How the camera turns light into DN. + pub camera: CameraTier, +} + +impl fmt::Display for LightingTier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::L0 => "L0", + Self::L1 => "L1", + }) + } +} + +impl fmt::Display for CameraTier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::C0 => "C0", + }) + } +} + +impl FromStr for LightingTier { + type Err = String; + + /// `L0` or `L1`, either case. + fn from_str(s: &str) -> Result { + match s.to_ascii_uppercase().as_str() { + "L0" => Ok(Self::L0), + "L1" => Ok(Self::L1), + _ => Err(format!("unknown lighting tier {s:?}: L0 or L1")), + } + } +} + +impl FromStr for CameraTier { + type Err = String; + + /// `C0`, either case. + fn from_str(s: &str) -> Result { + match s.to_ascii_uppercase().as_str() { + "C0" => Ok(Self::C0), + _ => Err(format!("unknown camera tier {s:?}: C0")), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tiers_parse_and_print() { + assert_eq!("l1".parse::().unwrap(), LightingTier::L1); + assert_eq!(LightingTier::L1.to_string(), "L1"); + assert_eq!("C0".parse::().unwrap(), CameraTier::C0); + assert!("L9".parse::().is_err()); + assert!(LightingTier::L0 < LightingTier::L1); + assert_eq!(Fidelity::default().lighting, LightingTier::L0); + } +} diff --git a/crates/resolved/src/gpu/bodies.rs b/crates/resolved/src/gpu/bodies.rs index a1277c8..2ad20d5 100644 --- a/crates/resolved/src/gpu/bodies.rs +++ b/crates/resolved/src/gpu/bodies.rs @@ -11,6 +11,8 @@ use super::targets::{FrameTargets, DEPTH_COMPARE, DEPTH_FORMAT, RADIANCE_FORMAT} use super::GpuContext; use crate::bodies::{Appearance, EARTH_EQUATORIAL_RADIUS, EARTH_POLAR_RADIUS, MOON_RADIUS}; use crate::camera::Intrinsics; +use crate::earth::EarthTexture; +use crate::model::rgb_spectrum; use crate::radiometry::{Radiometry, AU, SUN_RADIUS}; use crate::scene::{Pose, Scene}; use crate::Error; @@ -21,8 +23,18 @@ pub const MAX_CHANNELS: usize = 4; /// Per-channel constants for the bodies that do not change from frame to frame. #[derive(Clone, Copy, Debug, PartialEq)] pub struct BodyRadiometry { - /// Channel radiance of Earth's surface facing the Sun at 1 AU: albedo S_k / pi. + /// Channel radiance of Earth's surface facing the Sun at 1 AU: albedo S_k / pi, + /// with `Appearance::earth_albedo`. Used when Earth has no texture. pub earth_lambert_1au: [f32; 4], + /// The same with albedo 1: what a texture's per-channel albedo multiplies. + pub earth_irradiance_1au: [f32; 4], + /// Per channel, the albedo of each RGB basis spectrum (see [`rgb_spectrum`]): column + /// j maps a texture's channel j (red, green, blue) to the camera's channels. + pub earth_rgb: [[f32; 4]; 3], + /// Per channel, the albedo of `Appearance::earth_haze`, added to a texture's. + pub earth_haze: [f32; 4], + /// Whether Earth is drawn from a texture. + pub earth_textured: bool, /// The same for the Moon. pub moon_lambert_1au: [f32; 4], /// Channel radiance of the solar disk. @@ -46,12 +58,28 @@ impl BodyRadiometry { } out }; + let albedo = |spectrum: &crate::spectrum::Spectrum| { + let mut out = [0.0; 4]; + for (k, a) in radiometry.effective_albedo(spectrum).iter().enumerate() { + out[k] = *a as f32; + } + out + }; + let basis = |j: usize| { + let mut rgb = [0.0; 3]; + rgb[j] = 1.0; + albedo(&rgb_spectrum(rgb)) + }; let mut sun_radiance = [0.0; 4]; for (k, s) in sun_radiance.iter_mut().take(n).enumerate() { *s = radiometry.sun_radiance(k) as f32; } Ok(Self { earth_lambert_1au: lambert(radiometry.effective_albedo(&appearance.earth_albedo)), + earth_irradiance_1au: lambert(vec![1.0; n]), + earth_rgb: [basis(0), basis(1), basis(2)], + earth_haze: albedo(&appearance.earth_haze), + earth_textured: false, moon_lambert_1au: lambert(radiometry.effective_albedo(&appearance.moon_albedo)), sun_radiance, }) @@ -70,6 +98,10 @@ pub struct BodiesUniforms { earth_shape: [f32; 4], earth_sun: [f32; 4], earth_lambert: [f32; 4], + earth_irradiance: [f32; 4], + // mat3x4 columns: texture red, green, blue to channel albedo. + earth_rgb: [[f32; 4]; 3], + earth_haze: [f32; 4], moon_center: [f32; 4], moon_sun: [f32; 4], moon_lambert: [f32; 4], @@ -103,7 +135,12 @@ impl BodiesUniforms { Self { intrinsics: [intrinsics.fx, intrinsics.fy, intrinsics.cx, intrinsics.cy] .map(|x| x as f32), - params: [near as f32, 0.0, 0.0, 0.0], + params: [ + near as f32, + f32::from(u8::from(radiometry.earth_textured)), + 0.0, + 0.0, + ], earth_center: vec4(rel.earth.translation, EARTH_EQUATORIAL_RADIUS), earth_axes: [column(0), column(1), column(2)], earth_shape: [ @@ -114,6 +151,9 @@ impl BodiesUniforms { ], earth_sun: vec4(sun_from(Vector3::zeros()), 0.0), earth_lambert: scaled(radiometry.earth_lambert_1au, scene.sun.norm()), + earth_irradiance: scaled(radiometry.earth_irradiance_1au, scene.sun.norm()), + earth_rgb: radiometry.earth_rgb, + earth_haze: radiometry.earth_haze, moon_center: vec4(rel.moon, MOON_RADIUS), moon_sun: vec4(sun_from(scene.moon), 0.0), moon_lambert: scaled(radiometry.moon_lambert_1au, (scene.sun - scene.moon).norm()), @@ -128,6 +168,7 @@ impl BodiesUniforms { pub struct BodiesPass { pipeline: wgpu::RenderPipeline, uniforms: wgpu::Buffer, + sampler: wgpu::Sampler, bind_group: wgpu::BindGroup, } @@ -172,21 +213,99 @@ impl BodiesPass { usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("bodies"), - layout: &pipeline.get_bind_group_layout(0), - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: uniforms.as_entire_binding(), - }], + // Repeat across the 180 degree meridian, clamp at the poles. Not anisotropic: + // lavapipe's anisotropic filtering with explicit gradients falls back to coarse + // mips in places (measured as a color 26x off 13 px from the seam). + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("earth"), + address_mode_u: wgpu::AddressMode::Repeat, + address_mode_v: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::MipmapFilterMode::Linear, + ..Default::default() }); + // A 1 x 1 placeholder until a texture is set; the shader ignores it then. + let placeholder = EarthTexture::from_rgba8(1, 1, vec![0, 0, 0, 255]).expect("1 x 1"); + let bind_group = Self::bind(ctx, &pipeline, &uniforms, &sampler, &placeholder); Self { pipeline, uniforms, + sampler, bind_group, } } + /// Uploads `texture` with its mip chain and binds it for later frames. + pub fn set_earth_texture(&mut self, ctx: &GpuContext, texture: &EarthTexture) { + self.bind_group = Self::bind(ctx, &self.pipeline, &self.uniforms, &self.sampler, texture); + } + + fn bind( + ctx: &GpuContext, + pipeline: &wgpu::RenderPipeline, + uniforms: &wgpu::Buffer, + sampler: &wgpu::Sampler, + texture: &EarthTexture, + ) -> wgpu::BindGroup { + let levels = texture.mip_chain(); + let gpu = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: Some("earth"), + size: wgpu::Extent3d { + width: texture.width(), + height: texture.height(), + depth_or_array_layers: 1, + }, + mip_level_count: levels.len() as u32, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + // Linear reflectance, so filtering averages reflectance (see `mip_chain`). + format: wgpu::TextureFormat::Rgba16Float, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + for (level, (w, h, pixels)) in levels.iter().enumerate() { + ctx.queue.write_texture( + wgpu::TexelCopyTextureInfo { + texture: &gpu, + mip_level: level as u32, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + bytemuck::cast_slice(pixels), + wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(8 * w), + rows_per_image: Some(*h), + }, + wgpu::Extent3d { + width: *w, + height: *h, + depth_or_array_layers: 1, + }, + ); + } + let view = gpu.create_view(&Default::default()); + ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("bodies"), + layout: &pipeline.get_bind_group_layout(0), + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: uniforms.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(sampler), + }, + ], + }) + } + /// Uploads this frame's uniforms and records the pass into `encoder`, writing /// `timestamps` around it if given. pub fn encode( diff --git a/crates/resolved/src/lib.rs b/crates/resolved/src/lib.rs index 24ad84b..dcd405b 100644 --- a/crates/resolved/src/lib.rs +++ b/crates/resolved/src/lib.rs @@ -19,10 +19,12 @@ pub use wgpu; pub mod bodies; pub mod camera; +pub mod earth; pub mod eclipse; #[cfg(feature = "astrokit")] pub mod ephemeris; pub mod error; +pub mod fidelity; pub mod gpu; pub mod model; pub mod radiometry; @@ -32,7 +34,9 @@ mod solar_data; pub mod spectrum; pub use camera::{CameraConfig, Channel, Detector, Intrinsics}; +pub use earth::EarthTexture; pub use error::Error; +pub use fidelity::{CameraTier, Fidelity, LightingTier}; pub use model::{LoadOptions, Material, Mesh, ModelId}; pub use radiometry::Radiometry; pub use render::{Frame, Pending, Renderer, RendererOptions}; diff --git a/crates/resolved/src/model.rs b/crates/resolved/src/model.rs index e4e106b..8d2fc02 100644 --- a/crates/resolved/src/model.rs +++ b/crates/resolved/src/model.rs @@ -39,12 +39,9 @@ impl Material { /// does not determine a spectrum, so a material whose reflectance matters should be /// given one measured spectrum instead. pub fn from_rgb(name: &str, rgb: [f64; 3]) -> Self { - let [r, g, b] = rgb; - let nm = [200.0, 490.0, 500.0, 580.0, 590.0, 2800.0].map(|x| x * 1e-9); Self { name: name.to_owned(), - albedo: Spectrum::sampled(nm.to_vec(), vec![b, b, g, g, r, r]) - .expect("fixed wavelengths"), + albedo: rgb_spectrum(rgb), } } } @@ -55,6 +52,17 @@ impl Material { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct ModelId(pub usize); +/// The stepped spectrum an RGB reflectance stands for: blue below 490 nm, green from +/// 500 to 580 nm, red above 590 nm (into the infrared), with linear steps between. +/// +/// It is linear in `rgb`, so per-channel albedos of an RGB image can be folded into a +/// 3 x N matrix once (see [`rgb_spectrum`] with unit vectors) instead of per texel. +pub fn rgb_spectrum(rgb: [f64; 3]) -> Spectrum { + let [r, g, b] = rgb; + let nm = [200.0, 490.0, 500.0, 580.0, 590.0, 2800.0].map(|x| x * 1e-9); + Spectrum::sampled(nm.to_vec(), vec![b, b, g, g, r, r]).expect("fixed wavelengths") +} + /// A named piece of a model, drawn with one material. #[derive(Clone, Debug, PartialEq)] pub struct Part { diff --git a/crates/resolved/src/render.rs b/crates/resolved/src/render.rs index f2a5058..8a0a3ee 100644 --- a/crates/resolved/src/render.rs +++ b/crates/resolved/src/render.rs @@ -12,7 +12,11 @@ use nalgebra::Vector3; use crate::bodies::{Appearance, MOON_RADIUS}; use crate::camera::CameraConfig; +use std::sync::Arc; + +use crate::earth::EarthTexture; use crate::eclipse::sun_visible_fraction; +use crate::fidelity::{Fidelity, LightingTier}; use crate::gpu::bodies::{BodiesPass, BodiesUniforms, BodyRadiometry}; use crate::gpu::mesh::{GpuMesh, MeshDraw, MeshPass}; use crate::gpu::profile::{PassTimings, Profiler}; @@ -25,10 +29,14 @@ use crate::scene::{Pose, Scene}; use crate::Error; /// Choices made once, when the renderer is created. -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default)] pub struct RendererOptions { /// Which device to render on. pub adapter: AdapterChoice, + /// The lighting and camera tiers to draw at. + pub fidelity: Fidelity, + /// Earth's surface, needed from lighting tier L1 up; ignored below it. + pub earth_texture: Option>, /// How Earth and the Moon reflect light. pub appearance: Appearance, /// Also read back the channel radiance image each frame. Costs another 16 bytes per @@ -73,6 +81,7 @@ impl Frame { #[derive(Debug)] pub struct Renderer { ctx: GpuContext, + fidelity: Fidelity, camera: CameraConfig, radiometry: Radiometry, body_radiometry: BodyRadiometry, @@ -117,17 +126,24 @@ impl Renderer { row, ) }); + if options.fidelity.lighting >= LightingTier::L1 && options.earth_texture.is_none() { + return Err(Error::MissingInput(format!( + "lighting {} draws Earth's surface from an image: set RendererOptions::earth_texture", + options.fidelity.lighting + ))); + } let profiler = if options.profile { Profiler::new(&ctx) } else { None }; - Ok(Self { + let mut renderer = Self { bodies: BodiesPass::new(&ctx), meshes: MeshPass::new(&ctx), sensor: SensorPass::new(&ctx), sensor_uniforms: SensorUniforms::new(&camera, &radiometry), ctx, + fidelity: options.fidelity, camera, radiometry, body_radiometry, @@ -136,7 +152,11 @@ impl Renderer { dn_staging, radiance_staging, profiler, - }) + }; + if let Some(texture) = &options.earth_texture { + renderer.set_earth_texture(texture); + } + Ok(renderer) } /// The device this renders on. @@ -149,6 +169,29 @@ impl Renderer { &self.camera } + /// Draws Earth's surface from `texture` from now on, replacing the one given in + /// [`RendererOptions::earth_texture`]: for switching months. Uploads the texture + /// with its mip chain, which for a 16384 x 8192 image is about 1.4 GB of GPU memory + /// and 0.4 s. + /// + /// Below lighting tier L1 there is no Earth texture, and this does nothing. + pub fn set_earth_texture(&mut self, texture: &EarthTexture) { + if self.fidelity.lighting < LightingTier::L1 { + log::warn!( + "lighting {} draws no Earth texture; ignoring it", + self.fidelity.lighting + ); + return; + } + self.bodies.set_earth_texture(&self.ctx, texture); + self.body_radiometry.earth_textured = true; + } + + /// The tiers this renderer draws at. + pub fn fidelity(&self) -> Fidelity { + self.fidelity + } + /// Uploads a model, to be drawn by any [`Target`](crate::Target) that names the /// returned id. pub fn load_model(&mut self, mesh: &Mesh) -> Result { diff --git a/crates/resolved/src/shaders/bodies.wgsl b/crates/resolved/src/shaders/bodies.wgsl index 9a7e7f6..5e8efe4 100644 --- a/crates/resolved/src/shaders/bodies.wgsl +++ b/crates/resolved/src/shaders/bodies.wgsl @@ -8,7 +8,7 @@ struct Uniforms { // fx, fy, cx, cy in pixels. intrinsics: vec4, - // x: near clip distance. + // x: near clip distance. y: 1 if Earth is drawn from the texture. params: vec4, // xyz: Earth center. w: equatorial radius. earth_center: vec4, @@ -18,8 +18,15 @@ struct Uniforms { earth_shape: vec4, // xyz: unit vector from Earth toward the Sun. earth_sun: vec4, - // Per channel: channel radiance of the surface facing the Sun (albedo S_k / (pi d^2)). + // Per channel: channel radiance of the surface facing the Sun (albedo S_k / (pi d^2)), + // for the untextured Earth. earth_lambert: vec4, + // The same with albedo 1, for the textured Earth. + earth_irradiance: vec4, + // Columns: per-channel albedo of texture red, green and blue reflectance. + earth_rgb: mat3x4, + // Per channel: albedo added to the texture's (stand-in for the atmosphere). + earth_haze: vec4, // xyz: Moon center. w: radius. moon_center: vec4, moon_sun: vec4, @@ -31,6 +38,11 @@ struct Uniforms { } @group(0) @binding(0) var u: Uniforms; +// Equirectangular reflectance: longitude -180..180 left to right, latitude +90 at the top. +@group(0) @binding(1) var earth_texture: texture_2d; +@group(0) @binding(2) var earth_sampler: sampler; + +const PI: f32 = 3.14159265358979; struct Out { @location(0) radiance: vec4, @@ -63,6 +75,27 @@ fn sphere_hit(o: vec3, d: vec3, r: f32) -> f32 { return select(MISS, t, t > 0.0); } +// Linear RGB reflectance at the Earth-fixed unit normal `n`. +// +// Longitude wraps at 180 degrees, where the texture coordinate jumps from 1 to 0 and its +// screen-space derivative with it, which would select the coarsest mip along the seam. +// So the coordinate is computed two ways, wrapping at 180 and at 0 degrees, and the one +// whose derivative is smaller here is used: both name the same texel under the repeat +// sampler (Tarini's method). +fn sample_earth(n: vec3) -> vec3 { + let x = atan2(n.y, n.x) / (2.0 * PI) + 0.5; + let v = 0.5 - asin(clamp(n.z, -1.0, 1.0)) / PI; + let ua = fract(x); + let ub = fract(x + 0.5) - 0.5; + let ga = vec2(dpdx(ua), dpdy(ua)); + let gb = vec2(dpdx(ub), dpdy(ub)); + let gv = vec2(dpdx(v), dpdy(v)); + let use_b = dot(gb, gb) < dot(ga, ga); + let uu = select(ua, ub, use_b); + let gu = select(ga, gb, use_b); + return textureSampleGrad(earth_texture, earth_sampler, vec2(uu, v), vec2(gu.x, gv.x), vec2(gu.y, gv.y)).rgb; +} + @fragment fn fs_main(@builtin(position) pos: vec4) -> Out { let k = u.intrinsics; @@ -80,12 +113,20 @@ fn fs_main(@builtin(position) pos: vec4) -> Out { let ds = dir * stretch; let ls = length(ds); let te = sphere_hit(to_cam * stretch, ds / ls, u.earth_center.w) / ls; + // The ellipsoid normal in Earth-fixed axes; its direction gives geodetic latitude. + // Computed for every pixel (misses use the camera's position and ignore the result) + // because texture gradients may only be taken in uniform control flow. + let n_fixed = normalize((to_cam + max(te, 0.0) * dir) * stretch * stretch); + let earth_rgb = sample_earth(n_fixed); if te > 0.0 && te < best { best = te; - let p = to_cam + te * dir; - let n_fixed = normalize(p * stretch * stretch); let n = u.earth_axes * n_fixed; - radiance = u.earth_lambert * max(dot(n, u.earth_sun.xyz), 0.0); + let lit = max(dot(n, u.earth_sun.xyz), 0.0); + if u.params.y > 0.5 { + radiance = u.earth_irradiance * (u.earth_rgb * earth_rgb + u.earth_haze) * lit; + } else { + radiance = u.earth_lambert * lit; + } } let tm = sphere_hit(-u.moon_center.xyz, d, u.moon_center.w); diff --git a/crates/resolved/tests/earth_texture.rs b/crates/resolved/tests/earth_texture.rs new file mode 100644 index 0000000..b98a7db --- /dev/null +++ b/crates/resolved/tests/earth_texture.rs @@ -0,0 +1,308 @@ +//! Earth drawn from a texture: lookup by latitude and longitude, Earth's rotation, the +//! 180 degree seam and minification, on the GPU and on the CPU (lavapipe). + +mod common; + +use resolved::bodies::Appearance; +use resolved::gpu::AdapterChoice; +use resolved::model::rgb_spectrum; +use resolved::nalgebra::{UnitQuaternion, Vector3}; +use resolved::radiometry::{Radiometry, AU}; +use std::sync::Arc; + +use resolved::{ + CameraConfig, EarthTexture, Fidelity, LightingTier, Pose, Renderer, RendererOptions, Scene, + Spectrum, +}; + +const ADAPTERS: [AdapterChoice; 2] = [AdapterChoice::Gpu, AdapterChoice::Cpu]; + +/// The camera's distance from Earth's center: Earth is about 580 px across. +const RANGE: f64 = 2.0e8; + +/// A lighting tier L1 renderer drawing Earth from `texture`. +fn renderer(adapter: &AdapterChoice, haze: bool, texture: &EarthTexture) -> Renderer { + let mut appearance = Appearance::default(); + if !haze { + appearance.earth_haze = Spectrum::constant(0.0); + } + let options = RendererOptions { + adapter: adapter.clone(), + fidelity: Fidelity { + lighting: LightingTier::L1, + ..Default::default() + }, + earth_texture: Some(Arc::new(texture.clone())), + appearance, + read_radiance: true, + ..Default::default() + }; + Renderer::new(CameraConfig::example(), options).unwrap() +} + +/// A unit vector at geocentric latitude and longitude, in degrees. +fn direction(lat: f64, lon: f64) -> Vector3 { + let (lat, lon) = (lat.to_radians(), lon.to_radians()); + Vector3::new(lat.cos() * lon.cos(), lat.cos() * lon.sin(), lat.sin()) +} + +/// Earth turned by `earth_attitude`, the camera over `lat`, `lon` (in inertial axes), +/// the Sun straight behind the camera. +fn view(lat: f64, lon: f64, earth_attitude: UnitQuaternion) -> (Scene, Pose) { + let up = direction(lat, lon); + let scene = Scene { + earth_attitude, + sun: up * AU, + moon: Vector3::new(0.0, 0.0, -3.844e8), + targets: vec![], + }; + let north = if lat.abs() > 80.0 { + Vector3::x() + } else { + Vector3::z() + }; + let camera = Pose::looking_at(up * RANGE, Vector3::zeros(), north).unwrap(); + (scene, camera) +} + +fn linear(byte: u8) -> f64 { + let v = byte as f64 / 255.0; + if v <= 0.040_45 { + v / 12.92 + } else { + ((v + 0.055) / 1.055).powf(2.4) + } +} + +/// Channel radiance of Earth's surface with sRGB-encoded reflectance `rgb`, facing the +/// Sun at 1 AU, without haze. +fn expected(rgb: [u8; 3]) -> [f64; 3] { + let r = Radiometry::new(&CameraConfig::example()); + let albedo = r.effective_albedo(&rgb_spectrum([rgb[0], rgb[1], rgb[2]].map(linear))); + [0, 1, 2].map(|k| r.lambert_radiance(k, albedo[k], 1.0, AU)) +} + +/// Eight 90-degree regions: columns are longitude -180..-90, -90..0, 0..90, 90..180; +/// rows are the northern then the southern hemisphere. +const REGIONS: [[u8; 3]; 8] = [ + [200, 40, 40], + [40, 200, 40], + [40, 40, 200], + [200, 200, 40], + [40, 200, 200], + [200, 40, 200], + [120, 120, 120], + [250, 250, 250], +]; + +/// The eight regions at 16 x 16 texels each, so a sample near a region's center has +/// only that region's color around it. (With one texel per region, the half-pixel +/// between the boresight and the center pixel's line of sight is enough for bilinear +/// filtering to mix in 0.1 % of a neighbor, a few percent in a dark channel.) +fn regions() -> EarthTexture { + let (w, h) = (64u32, 32u32); + let pixels = (0..h) + .flat_map(|y| (0..w).map(move |x| REGIONS[(y / 16 * 4 + x / 16) as usize])) + .flat_map(|[r, g, b]| [r, g, b, 255]) + .collect(); + EarthTexture::from_rgba8(w, h, pixels).unwrap() +} + +fn center(frame: &resolved::Frame) -> [f32; 4] { + frame.radiance.as_ref().unwrap()[540 * 1920 + 960] +} + +fn assert_close(got: [f32; 4], want: [f64; 3], context: &str) { + for c in 0..3 { + let error = got[c] as f64 / want[c] - 1.0; + assert!( + error.abs() < 1e-3, + "{context} channel {c}: {} vs {} ({:+.4} %)", + got[c], + want[c], + 100.0 * error + ); + } +} + +#[test] +fn texel_under_the_camera_sets_the_radiance() { + // Over 45 N 45 E the camera sees the middle of region 2 (longitude 0..90, north). + let _devices = common::device_lock(); + for adapter in &ADAPTERS { + let mut renderer = renderer(adapter, false, ®ions()); + let (scene, camera) = view(45.0, 45.0, UnitQuaternion::identity()); + let frame = renderer.submit(&scene, &camera).unwrap().wait().unwrap(); + assert_close( + center(&frame), + expected(REGIONS[2]), + &renderer.adapter_info().name, + ); + } +} + +#[test] +fn earth_rotation_moves_the_surface() { + // Turning Earth 90 degrees east about its axis puts longitude -45 under a camera + // fixed in inertial space at 45 E: region 1. And the south: region 5. + let _devices = common::device_lock(); + let turn = UnitQuaternion::from_axis_angle(&Vector3::z_axis(), 90f64.to_radians()); + for adapter in &ADAPTERS { + let mut renderer = renderer(adapter, false, ®ions()); + for (lat, region) in [(45.0, 1), (-45.0, 5)] { + let (scene, camera) = view(lat, 45.0, turn); + let frame = renderer.submit(&scene, &camera).unwrap().wait().unwrap(); + let name = &renderer.adapter_info().name; + assert_close( + center(&frame), + expected(REGIONS[region]), + &format!("{name} lat {lat}"), + ); + } + } +} + +#[test] +fn haze_adds_its_spectrum() { + let _devices = common::device_lock(); + let r = Radiometry::new(&CameraConfig::example()); + let haze = r.effective_albedo(&Appearance::default().earth_haze); + let surface = r.effective_albedo(&rgb_spectrum(REGIONS[2].map(linear))); + let want = [0, 1, 2].map(|k| r.lambert_radiance(k, surface[k] + haze[k], 1.0, AU)); + // Bluer haze: it raises blue the most. + assert!(haze[2] > haze[1] && haze[1] > haze[0], "{haze:?}"); + for adapter in &ADAPTERS { + let mut renderer = renderer(adapter, true, ®ions()); + let (scene, camera) = view(45.0, 45.0, UnitQuaternion::identity()); + let frame = renderer.submit(&scene, &camera).unwrap().wait().unwrap(); + assert_close(center(&frame), want, &renderer.adapter_info().name); + } +} + +#[test] +fn no_seam_at_180_degrees() { + // The four texel columns either side of 180 degrees (22.5 degrees each way) are one + // color and everything else another, so a seam that fell back to a coarse mip would + // show the average. Pixels near the image center, over 180 degrees, all read the + // edge color. + let _devices = common::device_lock(); + let (w, h) = (64u32, 32u32); + let (edge, middle) = ([30u8, 60, 200], [220u8, 220, 220]); + let pixels = (0..h) + .flat_map(|_| { + (0..w).map(|x| { + if !(4..w - 4).contains(&x) { + edge + } else { + middle + } + }) + }) + .flat_map(|[r, g, b]| [r, g, b, 255]) + .collect(); + let texture = EarthTexture::from_rgba8(w, h, pixels).unwrap(); + let want = expected(edge); + for adapter in &ADAPTERS { + let mut renderer = renderer(adapter, false, &texture); + // Over 179.8 E the seam is about one pixel right of center, inside a 2 x 2 pixel + // quad (screen-space derivatives are taken per quad). Over exactly 180 it would + // fall on a quad boundary, where even unhandled it shows nothing. + let (scene, camera) = view(0.0, 179.8, UnitQuaternion::identity()); + let frame = renderer.submit(&scene, &camera).unwrap().wait().unwrap(); + let radiance = frame.radiance.unwrap(); + let name = &renderer.adapter_info().name; + // 20 px from the disk center is 4 degrees of longitude, well inside the edge + // columns' texel centers. + for v in (440..640).step_by(10) { + for u in 940..980 { + let got = radiance[v * 1920 + u]; + // Shading falls off away from the sub-solar point; compare the color + // (channel ratios), which the texture alone sets. + let ratio = |a: usize, b: usize| got[a] as f64 / got[b] as f64; + let want_ratio = |a: usize, b: usize| want[a] / want[b]; + for (a, b) in [(0, 2), (1, 2)] { + let error = ratio(a, b) / want_ratio(a, b) - 1.0; + assert!( + error.abs() < 0.01, + "{name} pixel ({u}, {v}): ratio {a}/{b} off by {:+.2} %", + 100.0 * error + ); + } + } + } + } +} + +#[test] +fn far_views_average_rather_than_alias() { + // A checker of black and white texels, each 0.18 degrees (20 km), seen from 400,000 + // km where a pixel spans 44 km: mipmapping averages the checker to 50 % reflectance. + // Point sampling would make noise of 0 % and 100 %. + let _devices = common::device_lock(); + let (w, h) = (2048u32, 1024u32); + let pixels = (0..h) + .flat_map(|y| (0..w).map(move |x| if (x + y) % 2 == 0 { 0u8 } else { 255 })) + .flat_map(|v| [v, v, v, 255]) + .collect(); + let texture = EarthTexture::from_rgba8(w, h, pixels).unwrap(); + // 50 % linear reflectance is sRGB 188. + let gray = EarthTexture::from_rgba8(1, 1, vec![188, 188, 188, 255]).unwrap(); + for adapter in &ADAPTERS { + let mut renderer = renderer(adapter, false, &gray); + let (scene, mut camera) = view(10.0, 30.0, UnitQuaternion::identity()); + camera.position *= 2.0; + let mut block = |texture: &EarthTexture| { + renderer.set_earth_texture(texture); + let frame = renderer.submit(&scene, &camera).unwrap().wait().unwrap(); + let radiance = frame.radiance.unwrap(); + (520..560) + .flat_map(|v| (940..980).map(move |u| (u, v))) + .map(|(u, v)| radiance[v * 1920 + u][1] as f64) + .collect::>() + }; + let checker = block(&texture); + let reference = block(&gray); + let name = renderer.adapter_info().name.clone(); + for (c, g) in checker.iter().zip(&reference) { + let error = c / g - 1.0; + assert!( + error.abs() < 0.03, + "{name}: {c} vs {g} ({:+.2} %)", + 100.0 * error + ); + } + } +} + +#[test] +fn lighting_l1_needs_an_earth_texture() { + let _devices = common::device_lock(); + let options = RendererOptions { + fidelity: Fidelity { + lighting: LightingTier::L1, + ..Default::default() + }, + ..Default::default() + }; + let error = Renderer::new(CameraConfig::example(), options) + .unwrap_err() + .to_string(); + assert!(error.contains("earth_texture"), "{error}"); +} + +#[test] +fn lighting_l0_ignores_an_earth_texture() { + // L0 draws Earth from Appearance::earth_albedo whether or not a texture is given. + let _devices = common::device_lock(); + let (scene, camera) = view(45.0, 45.0, UnitQuaternion::identity()); + let center_at = |earth_texture: Option>| { + let options = RendererOptions { + earth_texture, + read_radiance: true, + ..Default::default() + }; + let mut renderer = Renderer::new(CameraConfig::example(), options).unwrap(); + center(&renderer.submit(&scene, &camera).unwrap().wait().unwrap()) + }; + assert_eq!(center_at(Some(Arc::new(regions()))), center_at(None)); +} diff --git a/docs/images/l0-c0.png b/docs/images/l0-c0.png index f040e39f5e43b0d7da0e449508ce288c78e9b126..441994f609b608d0272a620fbd7eb0ec5d774770 100644 GIT binary patch literal 53062 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 Plnh5;77kT{j?z6DcDi7jW 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 WI3l9+Y*u;z0000= 0.85) and +best lights the spacecraft's three visible faces, without putting the +spacecraft in Earth's shadow. With the Sun that high over the ground, one face +is lit at a slant. Model centers and radii were measured from the decoded files at the listed scales (the bounding box of every vertex); remeasure if a model changes. @@ -18,6 +24,10 @@ import pathlib import textwrap RE = 6378137.0 +# The ground at the image center, geocentric latitude and longitude in degrees. +GROUND = (25.0, 33.0) +# The least cos(incidence) the Sun may have on that ground. +GROUND_SUN = 0.85 AU = 1.495978707e11 LEO = 7.0e6 GEO = 42_164_000.0 @@ -105,20 +115,29 @@ def scene(name, file, scale, center, radius, orbit, description): offset = mul(attitude, center) position = [look[i] - offset[i] for i in range(3)] - to_limb = [-math.cos(limb), math.sin(limb), 0.0] - reach = math.sqrt(orbit * orbit - RE * RE) - limb_normal = norm([look[i] + reach * to_limb[i] for i in range(3)]) + # The ground behind the spacecraft at the image center: where the boresight, + # continued past the spacecraft, meets Earth. + b = dot(look, boresight) + reach = -b - math.sqrt(b * b - (dot(look, look) - RE * RE)) + ground = norm([look[i] + reach * boresight[i] for i in range(3)]) + # Turn Earth so GROUND is there, with north along the camera's view across it. + lat, lon = (math.radians(x) for x in GROUND) + fixed = [math.cos(lat) * math.cos(lon), math.cos(lat) * math.sin(lon), math.sin(lat)] + north = norm([boresight[i] - dot(boresight, ground) * ground[i] for i in range(3)]) + earth = triad(fixed, [0, 0, 1], ground, north) + best = None - for az in range(0, 360, 3): - for el in range(-87, 90, 3): + for az in range(0, 360, 2): + for el in range(-88, 90, 2): a, e = math.radians(az), math.radians(el) sun = [math.cos(e) * math.cos(a), math.cos(e) * math.sin(a), math.sin(e)] if dot(sun, [-1.0, 0.0, 0.0]) > math.cos(limb): continue # behind Earth: the spacecraft would be in shadow + if dot(ground, sun) < GROUND_SUN: + continue faces = [dot(mul(attitude, n), sun) for n in ([1, 0, 0], [0, 1, 0], [0, 0, 1])] - score = min(min(faces), dot(limb_normal, sun)) - if best is None or score > best[0]: - best = (score, sun) + if best is None or min(faces) > best[0]: + best = (min(faces), sun) sun = best[1] fmt = lambda v, d=2: "[" + ", ".join(f"{x:.{d}f}" for x in v) + "]" @@ -131,15 +150,22 @@ def scene(name, file, scale, center, radius, orbit, description): return f"""{header} # # resolved render --scene examples/scenes/{name}.toml --preview {name}.png +# resolved render --scene examples/scenes/{name}.toml --lighting L1 --preview {name}.png sun = {fmt([AU * x for x in sun], 0)} moon = [0, 0, -3.844e8] +# Earth-fixed to inertial: 25 N 33 E behind the spacecraft, looking north. +earth_attitude = {fmt(quaternion(earth), 9)} [observer] position = {fmt(camera)} look_at = {fmt(look)} up = [1, 0, 0] +# At lighting L1, Earth's surface shows July (from scripts/fetch-earth.sh). +[appearance] +earth_month = 7 + [[target]] model = "../models/nasa/{file}" scale = {scale}