240 lines
12 KiB
Markdown
240 lines
12 KiB
Markdown
# Rendering guide
|
||
|
||
See [README.md](README.md) for the project overview and a first 2MASS render,
|
||
and [build.md](build.md) for compilation. Commands below run from the repository
|
||
root and use the default Release binaries. Run a binary with `--help` for its
|
||
complete option list and defaults.
|
||
|
||
## Catalog input
|
||
|
||
Use `--all-sky-catalog assets/2mass/processed/all_sky` for the tiled 2MASS
|
||
catalog, or `--catalog PATH` for a single processed CSV. Acquisition and
|
||
processing are documented in [assets/2mass/README.md](assets/2mass/README.md).
|
||
2MASS blackbody amplitudes need a much larger display exposure than the
|
||
synthetic test catalog; `--exposure 1e15` is a starting point, to be adjusted
|
||
for the field and camera.
|
||
|
||
## Schwarzschild camera
|
||
|
||
`schwarzschild_sky` uses an analytic Schwarzschild metric in Cartesian ingoing
|
||
Kerr–Schild coordinates, with mass `M=1`. The default camera is static at
|
||
coordinate radius `30`. `--look-ra-deg` and `--look-dec-deg` set the direction
|
||
from the camera to the hole; the camera is placed on the opposite side of the
|
||
origin and points radially inward. `--observer-radius R` requires `R > 2`.
|
||
`--observer-inward-speed V` applies a local inward boost relative to the static
|
||
observer, with `0 <= V < 1` and default `0`.
|
||
|
||
The analytic setup uses escape radius `256` and capture radius `1.5`, inside
|
||
the horizon at `r=2`. These are current demonstration settings; they do not
|
||
establish termination criteria for future numerical-relativity data.
|
||
|
||
## Movie image sequences
|
||
|
||
Movie mode reads an observer-track CSV containing coordinate time, proper
|
||
time, Cartesian position, and a full four-by-four tetrad (21 columns). The
|
||
track must cover the requested frame times. The following generates a sample
|
||
accelerating Minkowski trajectory and renders it against the 2MASS sky:
|
||
|
||
```sh
|
||
mkdir -p output/imgs
|
||
./build/Release/minkowski_sky --write-minkowski-accel-track output/minkowski_accel_2s.csv \
|
||
--duration 2 --fps 30 --proper-acceleration 1.52
|
||
./build/Release/minkowski_sky --observer-track output/minkowski_accel_2s.csv \
|
||
--all-sky-catalog assets/2mass/processed/all_sky \
|
||
--frames-dir output/imgs --frames-prefix minkowski_accel \
|
||
--start-time 0 --duration 2 --fps 30 --exposure 1e15
|
||
```
|
||
|
||
The inclusive time interval produces frames `minkowski_accel_000000.png`
|
||
through `minkowski_accel_000060.png`. Exposure may need adjustment as Doppler
|
||
shifts change the brightness. The trajectory generator is a flat-spacetime
|
||
example; other camera motions are supplied through observer-track CSVs.
|
||
Output is an image sequence; video encoding is a separate step.
|
||
|
||
Movie rays from all frames share a newest-to-oldest coordinate-time sweep.
|
||
`--slab-duration` sets its time-slab width (default `64`). The current analytic
|
||
backends use logical slabs without metric I/O; [Nmesh](https://github.com/nmeshsource/nmesh) metric loading remains
|
||
future work.
|
||
|
||
## Reuse a completed lens map
|
||
|
||
`--lens-map-output FILE` saves the finalized inverse-lens mesh after tracing
|
||
and refinement, while also rendering the image normally. It stores the
|
||
geometry, frequency shifts, terminal statuses, triangle topology, and frame
|
||
metadata in a versioned `.grlens` file. This allows later catalog, PSF, and
|
||
exposure changes without repeating ray tracing.
|
||
|
||
```sh
|
||
mkdir -p output/imgs output/maps
|
||
./build/Release/schwarzschild_sky \
|
||
--all-sky-catalog assets/2mass/processed/all_sky \
|
||
--width 640 --height 360 --coarse-cell-pixels 8 --fov-deg 60 \
|
||
--refine-max-level 2 --exposure 1e15 \
|
||
--lens-map-output output/maps/schwarzschild.grlens \
|
||
--output output/imgs/schwarzschild_trace.png
|
||
./build/Release/schwarzschild_sky \
|
||
--lens-map-input output/maps/schwarzschild.grlens \
|
||
--all-sky-catalog assets/2mass/processed/all_sky \
|
||
--psf-fwhm-pixels 6 --psf-moffat-beta 3 --exposure 1e15 \
|
||
--output output/imgs/schwarzschild_restyled.png
|
||
```
|
||
|
||
Import and export are mutually exclusive. Imported maps retain their original
|
||
pixel dimensions, horizontal FOV, and mesh resolution; they cannot be refined
|
||
on import. Explicit conflicting dimensions or FOV are rejected. The reader
|
||
checks the format version, finite values, unit directions, triangle indices,
|
||
and per-frame CRCs.
|
||
|
||
A movie export stores all final frame meshes in one file. To render it again,
|
||
pass `--lens-map-input FILE`, the catalog, `--frames-dir DIR`, and
|
||
`--frames-prefix NAME`; no observer track is needed on import.
|
||
|
||
## Adaptive image mesh refinement
|
||
|
||
Adaptive refinement is disabled by default (`--refine-max-level 0`), so the
|
||
existing coarse-mesh renders remain unchanged. When enabled, its defaults are
|
||
an absolute direction error of `1e-3` degrees, relative error `0.1`, minimum
|
||
long edge `0.5` pixels, minimum area `0.25` pixel-squared, and a provisional
|
||
minimum discrete-Jacobian magnitude of `1e-3`. Each value can be overridden
|
||
independently:
|
||
|
||
```text
|
||
--refine-max-level N
|
||
--refine-angle-abs-deg D
|
||
--refine-angle-rel R
|
||
--refine-jacobian-min J
|
||
--refine-min-edge-pixels P
|
||
--refine-min-area-pixels2 A
|
||
```
|
||
|
||
`N` caps the triangle refinement level. Let `e` be the angle between the
|
||
traced longest-edge midpoint direction and the normalized endpoint
|
||
interpolation, and let `s` be the angle between those two endpoint **camera
|
||
directions**. Both are evaluated internally in radians; the absolute CLI
|
||
threshold `D` is specified in degrees and converted before comparison. `s`
|
||
is the angular geometric size of the image triangle's test edge, not a
|
||
source-sky/lens-map length. A locally escaped triangle is split only when
|
||
**both** `e > D_rad` (the converted `--refine-angle-abs-deg D`) and
|
||
`e / max(s, 1e-15) > --refine-angle-rel`. `P` and `A`
|
||
prevent selecting a leaf already at or below the requested image-plane
|
||
long-edge and area scales.
|
||
Triangles whose three vertices disagree between capture and escape are split
|
||
independently of the direction-error thresholds, allowing the mesh to follow a
|
||
shadow boundary.
|
||
|
||
Independently of the midpoint geometry test, an all-escaped triangle also
|
||
computes the discrete lens Jacobian
|
||
`J = Omega_source / Omega_image`. Both signed solid angles use
|
||
`2 atan2(dot(a, cross(b,c)), 1 + dot(a,b) + dot(b,c) + dot(c,a))`, with the
|
||
ordered camera directions for `Omega_image` and their traced infinity
|
||
directions for `Omega_source`. A J-driven split requires **both** a shared
|
||
image edge whose incident triangles have opposite nonzero signs of `J` and
|
||
`min(abs(J_left), abs(J_right)) < --refine-jacobian-min`. It then requests
|
||
that shared edge on both leaves. Thus `|J|` bounds the fold selection instead
|
||
of widening it as a standalone critical-curve band. A negative sign is
|
||
physical parity and is retained. The `1e-3` default is deliberately
|
||
provisional and should be tuned with the small Schwarzschild refinement
|
||
diagnostic before being treated as a production threshold.
|
||
|
||
For a small mesh diagnostic using the synthetic catalog:
|
||
|
||
```sh
|
||
./build/Release/schwarzschild_sky --catalog assets/sky_grid_5deg.csv \
|
||
--width 48 --height 48 --coarse-cell-pixels 24 --fov-deg 40 \
|
||
--refine-max-level 1 --refine-angle-abs-deg 0.001 \
|
||
--refine-angle-rel 0.001 --refine-jacobian-min 0.001 \
|
||
--refine-min-edge-pixels 1 \
|
||
--refine-min-area-pixels2 1 --draw-mesh \
|
||
--output output/imgs/schwarzschild_refinement.png
|
||
```
|
||
|
||
For movies, each refinement generation completes the full newest-to-oldest
|
||
time-slab sweep before any probe becomes a mesh vertex. Newly added vertices
|
||
are therefore traced only by the next generation; the renderer never returns
|
||
to a slab that has already been released. At the start of every generation,
|
||
newly inserted vertices and geometry-only longest-edge probes for its new
|
||
leaves are collected together, so both ray sets use the same parallel
|
||
`RayPool` pass.
|
||
|
||
## Camera directions and optics
|
||
|
||
Catalog directions and `--look-ra-deg`/`--look-dec-deg` use standard
|
||
right-handed ICRS Cartesian axes: `+X` is RA 0 degrees/Dec 0 degrees, `+Y` is
|
||
RA 90 degrees/Dec 0 degrees, and `+Z` is the north celestial pole. The local
|
||
camera axes are forward, celestial north, and celestial west, so an image with
|
||
north up has decreasing RA to the right. The defaults preserve the original
|
||
`-Z` view. `--exposure` converts a catalog's physical flux
|
||
normalization to the prototype HDR scale. The current synthetic catalog is
|
||
calibrated for default exposure `1e-3`; a 2MASS blackbody normalization in
|
||
steradians requires a much larger display exposure such as the example above.
|
||
The optics path
|
||
integrates each fitted Planck spectrum through CIE 1931 color-matching functions
|
||
and converts the resulting radiance to linear sRGB; it does not use an empirical
|
||
color-temperature RGB approximation.
|
||
|
||
Point sources use a flux-normalized circular Moffat PSF by default
|
||
(`--psf-fwhm-pixels 2.7 --psf-moffat-beta 4.5`). The FWHM matches the former
|
||
1.15-pixel Gaussian core while the Moffat wings remain continuous; both values
|
||
are display/optics calibration parameters.
|
||
|
||
The default renderer builds one immutable, process-wide 64-by-64 sub-pixel
|
||
Moffat lookup kernel. Its weights are pixel-area integrals and are bilinearly
|
||
interpolated between phase tables. The renderer reports its build time and
|
||
cached/direct-fallback image counts. Pass `--psf-direct` to use the slower
|
||
8-point quadrature reference evaluator for regression comparisons; an image
|
||
whose required HDR-tail support exceeds the cache radius selects that reference
|
||
path automatically.
|
||
|
||
`--psf-relative-tail R` controls the maximum omitted PSF tail fraction per
|
||
image; it defaults to `1e-8`. Larger values intentionally shorten the Moffat
|
||
support and rebuild the immutable cache at the corresponding radius, which is
|
||
useful when a faster, lower-fidelity render is acceptable.
|
||
|
||
`--psf-min-y Y` defaults to `0` (disabled). A positive value is a linear-HDR
|
||
luminance cutoff: a PSF stops where its continuous Moffat Y profile falls below
|
||
`Y`, and an image whose central value is already below `Y` is omitted. The
|
||
final PSF report counts such omitted events and emits a warning when any occur.
|
||
|
||
For bounded preview renders, `--max-cache-psf-flux F` (default `1`) allows
|
||
images with `1 < flux <= F` to use the existing cache instead of the direct
|
||
evaluator. This does not rebuild or enlarge the cache: the image retains its
|
||
true core flux, color, and sub-pixel position, while its Moffat wing is clipped
|
||
at the existing cache radius. Images above `F` retain the direct fallback.
|
||
`--max-magnification M` (default unlimited) caps the per-triangle rendering
|
||
magnification before flux is formed; it is an explicit preview approximation.
|
||
|
||
## Progress and diagnostics
|
||
|
||
The PSF-cache completion line is printed before tracing and catalog splatting
|
||
begin. For long renders, pass `--verbose` to print catalog-prefetch state,
|
||
splat-worker local heartbeats (8, 16, 32, ... completed triangles per worker),
|
||
and image-write boundaries. The worker heartbeats use neither global progress
|
||
accounting nor cross-worker synchronization.
|
||
Verbose ray-trace output reports the initial mesh trace and refinement stages
|
||
for single frames; movie mode additionally reports each generation's sample
|
||
count and each time slab's activation and terminal-ray summary.
|
||
Movie renders always print one summary per time slab; `--verbose` also prints
|
||
the ray counts before each slab is loaded.
|
||
|
||
Pass `--draw-mesh` to alpha-composite image-plane triangle edges as
|
||
one-pixel-wide 0.5 linear-gray diagnostic lines at 0.5 opacity. The line
|
||
rasterizer uses coverage-based antialiasing.
|
||
|
||
## HDR output
|
||
|
||
To preserve a single-frame render for later exposure and tone-mapping work,
|
||
build with `ENABLE_HDR=1` as described in [build.md](build.md). This produces `build/Release/schwarzschild_sky`,
|
||
which accepts `--hdr-output`. This switch writes the HDR file next to the
|
||
ordinary output, replacing its extension with `_HDR.fits`; for example,
|
||
`--output output/imgs/ring.png --hdr-output` writes
|
||
`output/imgs/ring_HDR.fits`. It writes the pre-tone-mapping RGB
|
||
framebuffer as a three-plane, 32-bit float FITS image. Values remain linear HDR
|
||
at the renderer's arbitrary scale; no tone mapping or per-frame normalization
|
||
is applied. The ordinary
|
||
binaries do not contain this option or writer.
|
||
|
||
The FITS header describes a synthetic 8640-by-5760, 36-by-24 mm full-frame
|
||
sensor with 4.1667 um pixels. Each render records its active centered crop and
|
||
derives `FOCALLEN` from that render's width and horizontal `--fov-deg`; these
|
||
camera fields support plate-solving workflows but do not calibrate flux.
|