163 lines
6.6 KiB
Markdown
163 lines
6.6 KiB
Markdown
# resolved
|
||
|
||
Renders what a spacecraft camera sees: Earth, Moon, Sun and a target
|
||
spacecraft loaded from a mesh. The inputs are an epoch, the observer and target
|
||
states and attitudes, and a camera model. Images are radiometric: the renderer
|
||
works in radiance and converts to electrons and then DN through the camera's
|
||
optics and detector, so an image should match what a real camera with the same
|
||
parameters records.
|
||
|
||
It is built for closed-loop simulation, where each step renders one frame and
|
||
latency is the number that matters. The goal is high-fidelity 1–2 MP RGB in
|
||
about 10 ms end to end on a mid-range GPU (the reference is an RTX 3060);
|
||
100 ms is the ceiling, not the aim. Lighting fidelity and camera fidelity are
|
||
selected separately, and each tier is benchmarked, so a run can trade
|
||
fidelity for speed knowing what each step costs.
|
||
|
||
Rendering goes through wgpu. On a machine without a GPU, the same shaders run
|
||
on the CPU through Mesa's lavapipe (`vulkan-swrast` on Arch,
|
||
`mesa-vulkan-drivers` on Debian and Ubuntu).
|
||
|
||
## Usage
|
||
|
||
```rust
|
||
use resolved::nalgebra::{UnitQuaternion, Vector3};
|
||
use resolved::radiometry::AU;
|
||
use resolved::{CameraConfig, Mesh, Pose, Renderer, Scene, Target};
|
||
|
||
# fn main() -> Result<(), resolved::Error> {
|
||
// Setup, once: device, shaders, spectra folded into the camera's channels.
|
||
let mut renderer = Renderer::new(CameraConfig::example(), Default::default())?;
|
||
|
||
// A model, uploaded once. Usually `Mesh::load(path, &options)` on an STL or glTF.
|
||
let stl = "solid panel
|
||
facet normal 0 0 0
|
||
outer loop
|
||
vertex 0 -1 -1
|
||
vertex 0 1 -1
|
||
vertex 0 0 1
|
||
endloop
|
||
endfacet
|
||
endsolid panel";
|
||
let panel = renderer.load_model(&Mesh::from_stl(&mut std::io::Cursor::new(stl), "panel", &Default::default())?)?;
|
||
|
||
// Each step: inertial positions in meters, Earth at the origin.
|
||
let target = Vector3::new(7.0e6, 0.0, 0.0);
|
||
let scene = Scene {
|
||
earth_attitude: UnitQuaternion::identity(),
|
||
sun: Vector3::new(AU, 0.0, 0.0),
|
||
moon: Vector3::new(0.0, 3.844e8, 0.0),
|
||
targets: vec![Target { model: panel, pose: Pose::new(target, UnitQuaternion::identity()) }],
|
||
};
|
||
let observer = Pose::looking_at(target + Vector3::new(20.0, 0.0, 0.0), target, Vector3::z()).unwrap();
|
||
|
||
let pending = renderer.submit(&scene, &observer)?;
|
||
// ... the simulation's own work for this step overlaps the GPU's here ...
|
||
let frame = pending.wait()?;
|
||
|
||
// 12-bit DN, channels r, g, b interleaved. The panel faces the Sun and the camera.
|
||
assert!(frame.dn_at(960, 540, 1) > 1000);
|
||
# Ok(())
|
||
# }
|
||
```
|
||
|
||
## Command line
|
||
|
||
`resolved render` renders a scene described in TOML: camera overrides, the
|
||
observer, Sun and Moon positions (or an epoch, with the `astrokit` feature) and
|
||
targets with their models. `examples/scenes/` has commented examples:
|
||
`boxwing.toml` and `boxwing-epoch.toml` use a small generated model that is
|
||
in the repository, and the rest use NASA spacecraft models (Landsat 8,
|
||
ICESat-2, OSTM/Jason-2, an SSL-1300 and ACE) that `scripts/fetch-models.sh`
|
||
downloads into `examples/models/nasa/`. That script needs Node (for `npx`),
|
||
because most NASA models are Draco-compressed and are decoded on the way in.
|
||
|
||
```sh
|
||
cargo run --release -p resolved-cli -- render \
|
||
--scene examples/scenes/boxwing.toml \
|
||
--out frame.png --preview preview.png --timings 50
|
||
```
|
||
|
||
- `--out` writes raw DN as a 16-bit PNG, shifted up to fill 16 bits as
|
||
cameras usually store them: a 12-bit DN is the stored value divided by 16.
|
||
It is linear, so it looks dark next to the preview.
|
||
- `--preview` writes an 8-bit sRGB image for looking at, with the bias as
|
||
black and saturation as white.
|
||
- `--timings N` renders N frames and reports setup and per-frame times.
|
||
- `--adapter` takes `auto`, `gpu`, `cpu` or part of an adapter's name;
|
||
`resolved adapters` lists them.
|
||
- `--kernels DIR` says where astrokit's kernels are, for scenes given by
|
||
epoch. Without it the CLI tries `ASTROKIT_KERNELS`, then `data/kernels`,
|
||
then the sibling `../astrokit` checkout it was built against.
|
||
|
||
## Features
|
||
|
||
- `astrokit`: build a `Scene` from an epoch with
|
||
[astrokit](https://git.rcjohnstone.com/connor/astrokit)'s ephemerides (JPL
|
||
DE440s for the Sun and Moon, ITRF93 for Earth's orientation), through
|
||
`ephemeris::Ephemeris`. astrokit is not on crates.io, so this expects a
|
||
checkout at `../astrokit` next to this repository, and its kernels from its
|
||
`scripts/fetch-kernels.sh` (or `ASTROKIT_KERNELS` pointing at them).
|
||
|
||
## Status
|
||
|
||
M1 in progress: the library renders Earth, Moon, Sun and targets to DN
|
||
through `Renderer`, and the CLI renders scene files; benchmarks are still to
|
||
come. The work is
|
||
tracked as Gitea milestones, each split into one issue per change:
|
||
|
||
| Milestone | Scope |
|
||
|---|---|
|
||
| M1 | Scene API, headless wgpu, analytic Earth/Moon/Sun, glTF and STL targets, Lambert shading with eclipse, pinhole camera, radiance to DN, CLI, benchmark baseline |
|
||
| M2 | Self-shadowing, Earthshine and Moonshine, Earth and Moon albedo maps, Sun limb darkening |
|
||
| M3 | Lens distortion, PSF, detector noise, saturation and blooming, stars |
|
||
| M4 | BRDF and spectral materials, atmospheric scattering, clouds, lunar terrain |
|
||
| M5 | Portable ray tracing, path tracing, label outputs for ML |
|
||
| M6 | Validation against lunar, Earth and Mitsuba references; motion blur, rolling shutter, stray light; multi-band and 4k |
|
||
|
||
## Conventions
|
||
|
||
- SI units throughout: meters, seconds, radians, watts.
|
||
- Scene state is f64 in an inertial frame (GCRF). The GPU only sees f32
|
||
coordinates relative to the camera.
|
||
|
||
## Layout
|
||
|
||
- `crates/resolved`: the library.
|
||
- `crates/resolved-cli`: the `resolved` command, for rendering without writing
|
||
a host program.
|
||
- `examples/`: scene files and the box-wing model
|
||
(`scripts/make-example-model.py` regenerates it). The NASA scenes are
|
||
generated by `scripts/make-example-scenes.py`.
|
||
- `scripts/`: generators for compiled-in data and examples.
|
||
- `docs/optics.md`: the optics and radiometry the renderer models, explained
|
||
for readers without an optics background.
|
||
|
||
## Development
|
||
|
||
Point git at the committed hooks once per clone:
|
||
|
||
```sh
|
||
git config core.hooksPath hooks
|
||
```
|
||
|
||
`pre-commit` runs formatting, clippy and the library unit tests; `pre-push`
|
||
runs the full test suite, including doctests, and the documentation build.
|
||
|
||
The integration tests render on both a hardware GPU and lavapipe, so running
|
||
them needs a Vulkan driver for the GPU and lavapipe installed. Set
|
||
`WGPU_BACKEND`, `WGPU_ADAPTER_NAME` and the other standard wgpu environment
|
||
variables to steer adapter discovery.
|
||
|
||
To see what the GPU tests render, set `RESOLVED_TEST_IMAGES=1`. Each test then
|
||
writes a PNG per device to `target/tmp/test-images/`, scaled so a white
|
||
surface facing the Sun is full white:
|
||
|
||
```sh
|
||
RESOLVED_TEST_IMAGES=1 cargo test --test bodies --test mesh
|
||
```
|
||
|
||
## License
|
||
|
||
MIT
|