Color antialiased half-edges by ray outcome with configurable Catppuccin colors and default opacity 0.5. Rasterize premultiplied RGBA8 overlays on the producer and composite in place after writing the clean image. Keep single-frame, movie, and replay output consistent. Add overlay, CLI, and queue ownership regressions and document the final output architecture.
269 lines
13 KiB
Markdown
269 lines
13 KiB
Markdown
# GR 4D ray tracing
|
||
|
||
**English** | [简体中文](README.zh-CN.md)
|
||
|
||
An offline spacetime renderer focused on physical accuracy. The project aims
|
||
to turn time-dependent numerical-relativity simulations, including binary
|
||
black-hole mergers, into 4K movies by tracing light rays backward through the
|
||
four-dimensional spacetime.
|
||
|
||
[](assets/images/schwarzschild_galactic_center.png)
|
||
|
||
*The 2MASS Galactic-center star field seen through Schwarzschild spacetime,
|
||
from a camera at radius 100 M. This reference image uses the legacy Reinhard
|
||
tone map (`--tone-map reinhard`). Click the image for the full 4K render;
|
||
[rendering command below](#example-galactic-center-field-through-schwarzschild-spacetime).*
|
||
|
||
Stars are individual catalog point sources with direction, temperature, and
|
||
amplitude. The renderer maps them into the camera image, accounting for
|
||
multiple images, gravitational lensing magnification, and frequency shifts,
|
||
then accumulates their sub-pixel point-spread functions (PSFs) into an HDR
|
||
image. Movie cameras are described by worldline and tetrad tracks.
|
||
|
||
Single images support independent coordinate position and look direction,
|
||
coordinate velocity, and camera roll in both backends. For example,
|
||
`--observer-position 1.75 0 0 --observer-velocity -0.5 0 0 --look-ra-deg 0 --look-dec-deg 0`
|
||
specifies an inward-moving Schwarzschild camera looking outward from inside
|
||
the horizon. See [single-frame camera parameters and complete commands](usage.md#single-frame-camera).
|
||
|
||
## Current status
|
||
|
||
The current implementation supports analytic **Minkowski**,
|
||
**Schwarzschild**, and moving **Alcubierre** warp-bubble spacetimes,
|
||
single images and observer-track image sequences,
|
||
adaptive lens meshes, and reusable lens-map files. It is written primarily in
|
||
C with OpenMP CPU parallelism; an optional HIP backend accelerates PSF
|
||
accumulation.
|
||
|
||
HIP retains parallel CPU catalog mapping and uses bounded, completion-protected
|
||
event uploads. See [HIP configuration and bounded performance checks](build.md#optional-hip-psf-acceleration).
|
||
|
||
The [Nmesh](https://github.com/nmeshsource/nmesh) numerical-spacetime backend and BBH rendering are still planned.
|
||
The current scope is black-hole shadows and distant stellar backgrounds;
|
||
local matter emission, accretion disks, and plasma are outside this stage.
|
||
See the [design document](nr_spacetime_movie_renderer_design.md) for the
|
||
architecture and development roadmap.
|
||
|
||
## Build
|
||
|
||
The default build requires a C11 compiler with OpenMP support, GNU Make, and
|
||
libpng development files. From the repository root:
|
||
|
||
```sh
|
||
make -j
|
||
```
|
||
|
||
This builds `build/Release/minkowski_sky`, `build/Release/schwarzschild_sky`,
|
||
and `build/Release/alcubierre_sky`. For individual backends, Debug builds,
|
||
optional HDR/FITS output, HIP support, and regression checks, see
|
||
[build.md](build.md).
|
||
|
||
## Prepare the stellar catalog
|
||
|
||
The renderer accepts **any stellar catalog converted to the supported CSV
|
||
format**: four columns containing ICRS right ascension and declination in
|
||
degrees, temperature in kelvin, and amplitude, in that order. Use
|
||
`--catalog PATH` to load a single CSV.
|
||
|
||
**2MASS is the recommended survey catalog**, with download and processing
|
||
scripts provided by this project. Instructions are in
|
||
[assets/2mass/README.md](assets/2mass/README.md).
|
||
The full download takes approximately **three to four days**, so **contact me
|
||
for a compressed archive first** if possible. Place the processed tile files
|
||
under `assets/2mass/processed/all_sky/` for the commands below.
|
||
|
||
The included [assets/sky_grid_5deg.csv](assets/sky_grid_5deg.csv) is a synthetic
|
||
stellar grid for geometry and regression tests. It can be used directly with
|
||
`--catalog assets/sky_grid_5deg.csv`, without downloading survey data; see the
|
||
[test-grid example below](#example-synthetic-test-grid-with-mesh-overlay).
|
||
|
||
## Rendering examples
|
||
|
||
The examples below illustrate a few choices of sky field, spacetime, and
|
||
camera settings. After building and preparing the catalog, adapt these
|
||
commands to your own field of view, observer position, and rendering settings.
|
||
See [usage.md](usage.md) for the available controls and workflows.
|
||
|
||
### Example: Summer Triangle in flat spacetime
|
||
|
||
This example renders a field around the Summer Triangle:
|
||
|
||
```sh
|
||
mkdir -p output/imgs
|
||
./build/Release/minkowski_sky \
|
||
--all-sky-catalog assets/2mass/processed/all_sky \
|
||
--look-ra-deg 296 --look-dec-deg 27 --fov-deg 72 \
|
||
--width 3840 --height 2160 --exposure 1e12 \
|
||
--tone-map reinhard \
|
||
--output output/imgs/summer_triangle.png
|
||
```
|
||
|
||
[](assets/images/summer_triangle.png)
|
||
|
||
*4K reference image with exposure `1e12`; it uses the legacy Reinhard tone
|
||
map (`--tone-map reinhard`). Click to view at full resolution.*
|
||
|
||
Angles are in degrees; `--fov-deg` is the horizontal field of view. Exposure
|
||
is an adjustable display multiplier. Use `--verbose` for progress during long
|
||
renders.
|
||
|
||
### Example: Galactic-center field through Schwarzschild spacetime
|
||
|
||
This example uses a camera at radius 100 M to render the lensed
|
||
Galactic-center field shown at the top of this page:
|
||
|
||
```sh
|
||
mkdir -p output/imgs
|
||
./build/Release/schwarzschild_sky \
|
||
--all-sky-catalog assets/2mass/processed/all_sky \
|
||
--width 3840 --height 2160 \
|
||
--look-ra-deg 262.5 --look-dec-deg -30 --fov-deg 45 \
|
||
--observer-radius 100 --exposure 1e13 \
|
||
--coarse-cell-pixels 16 --refine-max-level 4 --refine-jacobian-min 0.2 \
|
||
--psf-fwhm-pixels 2.7 --psf-moffat-beta 4.5 \
|
||
--psf-relative-tail 1e-8 --psf-min-y 0 --max-cache-psf-flux 1e8 \
|
||
--catalog-load-workers 4 \
|
||
--tone-map reinhard \
|
||
--output output/imgs/schwarzschild_galactic_center.png
|
||
```
|
||
|
||
This uses the updated reference image's rendering settings. The
|
||
`--max-cache-psf-flux 1e8` setting permits bright PSF wings to be clipped to the
|
||
cache radius, but this run reported no wing clipping or direct fallbacks.
|
||
The [CPU/HIP benchmark record](benchmarks/2mass_galactic_center_cpu_hip_2026-09-07.md)
|
||
preserves the commands and terminal output, and records bit-identical CPU/HIP
|
||
PNGs. The command above omits the optional HDR and lens-map exports.
|
||
|
||
This example infers a position facing the hole from its look direction and radius. Adaptive refinement should
|
||
be configured for the desired image accuracy; it is disabled by default.
|
||
Tone-mapped PNG/PPM output uses a per-channel soft-clip display transform by
|
||
default (`--tone-map softclip --tone-map-p 2`); `--tone-map reinhard` restores
|
||
the historical curve used by the reference images above. The linear HDR FITS
|
||
output never applies it. An optional limited-response sensor bloom
|
||
(`--sensor-bloom-limit E --sensor-bloom-transfer e`, default disabled) can be
|
||
applied to the linear HDR before display; it never changes the FITS output. See
|
||
[tone mapping and display](usage.md#tone-mapping-and-display).
|
||
Camera controls, movie sequences, lens-map reuse, PSF settings, and HDR output
|
||
are described in [usage.md](usage.md). For fast single-frame previews,
|
||
`--fast-mode` replaces per-event PSF splats with a supersampled delta deposit
|
||
plus one global PSF convolution and downsample. Its nearest-deposit path is not
|
||
qualified for temporally coherent movie output; see [usage.md](usage.md) for
|
||
the spatial and temporal accuracy limits. Both binaries provide a complete
|
||
option list with `--help`.
|
||
|
||
### Example: Looking outward just above the Schwarzschild horizon
|
||
|
||
This camera is static at `(2.1, 0, 0)` in Cartesian Kerr–Schild coordinates,
|
||
just outside the horizon at `r=2M` (`M=1`). RA=0°, Dec=0° points along +X,
|
||
radially outward. Its coordinate velocity is zero: this is a static
|
||
observer, not a freely falling one. The scene uses the bundled synthetic
|
||
stellar grid, so no survey download is needed.
|
||
|
||
```sh
|
||
mkdir -p output/imgs
|
||
./build/Release/schwarzschild_sky \
|
||
--catalog assets/sky_grid_5deg.csv \
|
||
--observer-position 2.1 0 0 \
|
||
--observer-velocity 0 0 0 \
|
||
--look-ra-deg 0 --look-dec-deg 0 \
|
||
--camera-roll-deg 0 \
|
||
--width 3840 --height 2160 --fov-deg 90 \
|
||
--coarse-cell-pixels 16 --refine-max-level 3 \
|
||
--exposure 0.01 \
|
||
--tone-map reinhard \
|
||
--output output/imgs/schwarzschild_near_horizon_outward_R2.1_0.01_refine3.png
|
||
```
|
||
|
||
[](assets/images/schwarzschild_near_horizon_outward_R2.1_0.01_refine3.png)
|
||
|
||
*4K render with a 90° horizontal field of view, exposure `0.01`, and maximum
|
||
refinement level 3; it uses the legacy Reinhard tone map (`--tone-map
|
||
reinhard`). Click to view at full resolution.*
|
||
|
||
The distant sky occupies a bounded angular region around the outward
|
||
direction, with repeated images crowded near its edge. Strong gravitational
|
||
blueshift pushes both the 3000 K and 12000 K test stars toward blue-white.
|
||
|
||
### Example: Synthetic test grid with mesh overlay
|
||
|
||
This example uses `assets/sky_grid_5deg.csv` to inspect lensing and adaptive
|
||
mesh refinement in Schwarzschild spacetime. The main output is the clean
|
||
tone-mapped image; `--draw-mesh` additionally writes the final image-plane
|
||
triangles, so one command produces both
|
||
`output/imgs/schwarzschild_test_grid.png` (no mesh) and
|
||
`output/imgs/schwarzschild_test_grid_mesh.png` (mesh overlay).
|
||
The antialiased mesh is drawn after tone mapping: gray escape half-edges,
|
||
purple dark half-edges, yellow budget-unresolved half-edges, and red failure
|
||
half-edges, using Catppuccin Mocha defaults. Color and opacity settings are
|
||
documented in `usage.md`.
|
||
|
||
```sh
|
||
mkdir -p output/imgs
|
||
./build/Release/schwarzschild_sky \
|
||
--max-cache-psf-flux 1e8 \
|
||
--catalog assets/sky_grid_5deg.csv \
|
||
--refine-max-level 3 --refine-jacobian-min 0.2 \
|
||
--width 3840 --height 2160 \
|
||
--look-ra-deg 0.1 --look-dec-deg 0.1 --fov-deg 45 \
|
||
--coarse-cell-pixels 32 \
|
||
--observer-radius 100 --exposure 0.2 \
|
||
--psf-fwhm-pixels 2.7 --psf-moffat-beta 4.5 --draw-mesh \
|
||
--tone-map reinhard \
|
||
--output output/imgs/schwarzschild_test_grid.png
|
||
```
|
||
|
||
[](assets/images/schwarzschild_test_grid_mesh.png)
|
||
|
||
*4K test-grid reference image showing the `_mesh.png` overlay; it uses the
|
||
legacy Reinhard tone map (`--tone-map reinhard`). Click to view at full
|
||
resolution.*
|
||
|
||
### Freely falling Schwarzschild movie camera
|
||
|
||
`scripts/schwarzschild_camera_track.py` generates the canonical 21-column observer
|
||
CSV in ingoing Cartesian Kerr–Schild coordinates (`G=c=M=1`, matching the renderer).
|
||
It requires Python 3, NumPy and SciPy. Position is `(x,y,z)`; velocity is coordinate
|
||
`(dx/dt,dy/dt,dz/dt)`. Look RA/Dec and roll construct the initial rest-frame
|
||
forward/up/right legs with the same convention as the single-image camera. Without
|
||
look angles, the camera initially points toward the origin. The orientation then
|
||
follows Fermi–Walker transport; for free fall this is parallel transport, so it does
|
||
not keep pointing at the black hole. `--tetrad` alternatively accepts 16 row-major
|
||
components `(e0t,e0x,...,e3z)` of an orthonormal tetrad, with `e0` matching velocity.
|
||
|
||
```sh
|
||
python3 scripts/schwarzschild_camera_track.py \
|
||
--position 8 0 0 --velocity 0 0 0 \
|
||
--look-ra-deg 0 --look-dec-deg 0 --roll-deg 0 \
|
||
--fps 30 --duration 10 --output /tmp/freefall_camera.csv
|
||
make SPACETIME=schwarzschild all
|
||
mkdir -p output/freefall_frames
|
||
./build/Release/schwarzschild_sky \
|
||
--observer-track /tmp/freefall_camera.csv --movie-track-samples \
|
||
--frames-dir output/freefall_frames --catalog assets/sky_grid_5deg.csv \
|
||
--width 640 --height 360 --fov-deg 60 --exposure 0.2
|
||
ffmpeg -framerate 30 -i output/freefall_frames/frame_%06d.png \
|
||
-c:v libx264 -pix_fmt yuv420p output/freefall.mp4
|
||
```
|
||
|
||
Here `--duration` is elapsed **proper time**, and `--fps` is samples per unit proper
|
||
time. CSV rows occur at `tau=k/fps <= duration`, including the initial event and an
|
||
endpoint only if it lies on that cadence (10 at 30 fps gives 301 rows). `tau` starts
|
||
at zero; `--t0` sets the initial coordinate time. `--movie-track-samples` uses each
|
||
row exactly once and ignores renderer `--start-time`, `--duration`, and `--fps`;
|
||
encode the PNG sequence at the generator's fps. Without this flag the existing
|
||
movie mode resamples at uniform coordinate time, which changes the proper-time cadence.
|
||
|
||
DOP853 jointly integrates the geodesic and all tetrad legs using analytic metric
|
||
derivatives. Defaults: `--rtol 1e-10 --atol 1e-12 --stop-radius 0.001`.
|
||
Integration crosses the horizon and stops at this numerical guard before the
|
||
singularity, reporting its proper time and retaining only regular cadence samples.
|
||
The guard is not the exact singularity; reduce it and tolerances to check convergence.
|
||
The renderer no longer uses a position capture cutoff: cameras at and inside the
|
||
old `r=1.5M` guard are valid targets, and a normal dark pixel comes from the
|
||
redshift-threshold truncation `log(alpha p^0) >= 8`. Budget-exhausted and
|
||
data/integration failures are separate unresolved/incomplete outcomes and are not
|
||
silently rendered as dark.
|
||
The script reports maximum tetrad drift and rejects errors above `1e-6` rather
|
||
than silently repairing the transported frame. Run the orbit, transport and CSV
|
||
render regressions after building with `python3 tests/test_schwarzschild_camera_track.py`.
|