diff --git a/README.md b/README.md index 51cd09b..4631adc 100644 --- a/README.md +++ b/README.md @@ -1,360 +1,150 @@ -# GR 4D ray tracing — Phase 0 prototype +# GR 4D ray tracing -`minkowski_sky` is a deliberately small, CPU-only, single-frame Phase 0 -benchmark. It renders point sources from a sky catalog through an analytic -backend. It is not a sky texture: each source remains a direction, temperature, -and amplitude until its sub-pixel Gaussian PSF is splatted. +**English** | [简体中文](README.zh-CN.md) -Build and render the default 1280 x 720 image: +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. + +[![Schwarzschild black-hole lensing of the 2MASS Galactic-center star field](assets/images/schwarzschild_galactic_center.png)](assets/images/schwarzschild_galactic_center.png) + +*The 2MASS Galactic-center star field seen through Schwarzschild spacetime, +from a camera at radius 100 M. 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. Moving cameras are described by worldline and tetrad tracks. + +## Current status + +The current implementation supports analytic **Minkowski** and +**Schwarzschild** 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. + +The [Nmesh](https://github.com/nmeshsource/nmesh) numerical-spacetime backend and BBH rendering are still planned. +The current scope is black-hole capture 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 run +make -j ``` -The program first creates `assets/sky_grid_5deg.csv` when it is missing. The -synthetic catalog places stars every 2 degrees on the union of longitude and -latitude lines spaced 10 degrees apart; the two poles are stored only once. -The eight octants (four 90-degree longitude sectors in each hemisphere) -alternate red `temperature_K = 3000` and blue `temperature_K = 12000`. -Red stars use `amplitude = 1`; blue stars use `amplitude = 0.00141095580387`, -which equalizes their CIE/linear-sRGB luminance under the renderer's blackbody -integration. Longitude boundaries belong to the sector to their east and the -equator to the northern hemisphere, so boundary stars have a deterministic -color. -The default output is PNG at `output/imgs/minkowski_sky.png`. +This builds both `build/Release/minkowski_sky` and +`build/Release/schwarzschild_sky`. For individual backends, Debug builds, +optional HDR/FITS output, HIP support, and regression checks, see +[build.md](build.md). -### Optional HIP PSF backend +## Prepare the stellar catalog -The default `PSF_BACKEND=cpu` uses the established OpenMP/private-HDR path. -Build the direct-atomic HIP PSF backend explicitly with -`PSF_BACKEND=hip`; it requires HIP/ROCm and a usable GPU agent, and writes a -separate `_hip` binary so it cannot overwrite the CPU renderer: +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 -make PSF_BACKEND=hip SPACETIME=minkowski backend -./build/Release/minkowski_sky_hip --catalog assets/sky_grid_5deg.csv \ - --output output/imgs/minkowski_sky_hip.png -``` - -The HIP backend accelerates only cache-eligible PSF events. Existing direct -fallbacks remain CPU reference evaluations, with an ordered HDR transfer before -and after each fallback. A HIP initialization, upload, kernel, or download -error terminates the render; it never switches to the CPU backend silently. -Use `make PSF_BACKEND=hip SPACETIME=minkowski hip-psf-test` for the small -GPU-vs-CPU cache-HDR regression. HIP renders also report event, batch, H2D, -kernel, and HDR-download timing after each frame. To bound diagnostic-event -storage, the report times at most the first 64 batches and labels the timed -batch count explicitly; the event and total-batch counts are not sampled. - -## Movie PNG sequence (Phase A) - -Movie mode consumes a canonical observer-track CSV rather than a fixed camera. -Each row stores coordinate time, proper time, Cartesian position, and the full -four-by-four tetrad (21 columns total). Generate the first reproducible -Minkowski benchmark—two coordinate seconds at 30 fps, accelerating from rest -to about `0.95c`—then render its numbered PNG frames: - -```sh -make clean && make mkdir -p output/imgs -./build/minkowski_sky --write-minkowski-accel-track output/minkowski_accel_2s.csv \ - --duration 2 --fps 30 --proper-acceleration 1.52 -./build/minkowski_sky --observer-track output/minkowski_accel_2s.csv \ - --frames-dir output/imgs --frames-prefix minkowski_accel \ - --start-time 0 --duration 2 --fps 30 --exposure 1e-5 +./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 \ + --output output/imgs/summer_triangle.png ``` -This writes `minkowski_accel_000000.png` through -`minkowski_accel_000060.png`. The renderer treats the CSV as its observer -input; the acceleration generator is only a reproducible flat-spacetime test -fixture. Movie mode collects all current frame-mesh vertices into a single -SoA ray pool, activates rays as a newest-to-oldest coordinate-time scan reaches -their observer event, and advances active rays to each slab boundary. The -analytic backends use logical slabs with no metric I/O; nmesh slab loading is -the next backend step. `--slab-duration` sets the coordinate-time width -(default `64`) for this current fixed-mesh pass. The current -synthetic test catalog uses global default exposure `1e-3`; the accelerated -benchmark explicitly uses `1e-5` because its physical Doppler blue shift -otherwise clips the later frames. +[![Summer Triangle in flat spacetime, exposure 1e12](assets/images/summer_triangle.png)](assets/images/summer_triangle.png) -## Reuse a completed lens map +*4K reference image with exposure `1e12`. Click to view at full resolution.* -Ray tracing and adaptive mesh refinement are independent of catalog lookup, -PSF evaluation, exposure, and tone mapping. `--lens-map-output FILE` writes -the finalized local inverse-lens mesh to one versioned `.grlens` file after all -ray-trace/refinement generations finish; the same invocation still renders its -ordinary image. The file stores every final vertex's image position, camera -direction, infinity direction, frequency shift, terminal status, and the -triangle topology. It also stores the frame dimensions, horizontal FOV, and, -for a movie, all frame IDs and times. +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. -For example, trace an analytic Schwarzschild frame once and retain the map: +### 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 -make SPACETIME=schwarzschild backend -mkdir -p output/imgs output/maps -./build/Release/schwarzschild_sky --catalog assets/sky_grid_5deg.csv \ - --width 640 --height 360 --coarse-cell-pixels 8 --fov-deg 60 \ - --refine-max-level 2 --lens-map-output output/maps/schwarzschild.grlens \ - --output output/imgs/schwarzschild_trace.png -``` - -Later, render that saved map with a different catalog, PSF, or exposure without -constructing an observer, spacetime source, or geodesic rays: - -```sh -./build/Release/schwarzschild_sky --lens-map-input output/maps/schwarzschild.grlens \ - --catalog assets/sky_grid_5deg.csv --psf-fwhm-pixels 6 --psf-moffat-beta 3 \ - --exposure 0.002 --output output/imgs/schwarzschild_restyled.png -``` - -`--lens-map-input` and `--lens-map-output` are mutually exclusive. An imported -map always retains its original pixel width, height, and horizontal FOV; an -explicit conflicting `--width`, `--height`, or `--fov-deg` is rejected. This is -intentional: the stored inverse map and PSF coordinates are in the original -pixel geometry. The reader validates the format version, finite values, unit -directions, triangle indices, and a per-frame CRC before rendering. - -A movie export writes every final frame mesh into the same `.grlens` file. -Export it during the usual movie render, then import it with `--frames-dir` and -`--frames-prefix`; importing a multi-frame map does not require -`--observer-track`: - -```sh -./build/Release/minkowski_sky --lens-map-input output/maps/movie.grlens \ - --catalog assets/sky_grid_5deg.csv --frames-dir output/imgs \ - --frames-prefix restyled -``` - -PNG is the default output and the default build links `libpng`: - -```sh -make clean && make mkdir -p output/imgs -./build/minkowski_sky --output output/imgs/minkowski_sky.png +./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 \ + --output output/imgs/schwarzschild_galactic_center.png ``` -If `libpng` is unavailable, rebuild with `make clean && make ENABLE_PNG=0`. -That intentionally selects the binary-PPM fallback, whose default path is -`output/imgs/minkowski_sky.ppm`; pass a `.ppm` path for explicit output. +This uses the reference image's rendering settings, including its +`--max-cache-psf-flux 1e8` preview approximation, which clips bright PSF wings +to the cache radius. The [original benchmark record](benchmarks/2mass_galactic_center_blackhole.md) +preserves the command and terminal output; the command above omits the optional +HDR export. -Run the flat-spacetime geodesic regression with: +The Schwarzschild camera points toward the hole. Adaptive refinement should +be configured for the desired image accuracy; it is disabled by default. +Camera controls, movie sequences, lens-map reuse, PSF settings, and HDR output +are described in [usage.md](usage.md). Both binaries provide a complete option +list with `--help`. + +### 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. `--draw-mesh` overlays the final +image-plane triangles. ```sh -make test -``` - -This also checks that the Kerr--Schild metric remains finite at `r=2M` and -that the central ray from the default Schwarzschild camera is classified as -captured. - -Build an independent analytic Schwarzschild executable in Cartesian ingoing -Kerr--Schild coordinates (regular at the horizon), then render the test catalog -to PNG: - -```sh -make clean && make SPACETIME=schwarzschild mkdir -p output/imgs -./build/schwarzschild_sky --catalog assets/sky_grid_5deg.csv \ - --width 640 --height 360 --coarse-cell-pixels 8 --fov-deg 60 \ - --output output/imgs/schwarzschild_test_catalog.png +./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 \ + --output output/imgs/schwarzschild_test_grid.png ``` -`SPACETIME=minkowski` (the default) and `SPACETIME=schwarzschild` select source -files at compile time, so each executable contains exactly one metric provider. -The Schwarzschild demonstration uses mass `M=1` and places a static camera at -coordinate radius `30` by default. `--look-ra-deg` and `--look-dec-deg` -define the direction from the camera to the hole; the camera is placed at the -opposite direction from the origin and its local forward axis points radially -inward. Use `--observer-radius R` to select any `R > 2`; it is a coordinate -radius in Cartesian Kerr--Schild coordinates. The backend escapes at `r=256` -and declares capture at `r=1.5`, safely inside the horizon at `r=2`. Those -rendering thresholds are Phase-1 demonstration values, not settled production -refinement or integration settings. +[![Synthetic stellar grid lensed by a Schwarzschild black hole, with adaptive mesh overlay](assets/images/schwarzschild_test_grid.png)](assets/images/schwarzschild_test_grid.png) -For a local radial boost relative to that static camera, pass -`--observer-inward-speed V`, where `0 <= V < 1` is measured in the static -observer's orthonormal frame and positive values point toward the hole. The -default is `0`, preserving the static camera. - -For rays that asymptote to the future horizon in coordinate-time backward -integration, the Schwarzschild demo also terminates at -`log(alpha p^0) = 8`. This is the normalized-momentum horizon diagnostic -already evolved by the integrator; it is disabled by default and does not -replace the AH-calibrated spatial capture criterion planned for nmesh data. - -Useful options: - -```sh -./build/minkowski_sky --width 1920 --height 1080 --fov-deg 30 \ - --catalog assets/sky_grid_5deg.csv --output output/imgs/frame.png -./build/minkowski_sky --catalog assets/2mass/processed/2mass_psc_m31_0p5deg_stars.csv \ - --look-ra-deg 10.6847083 --look-dec-deg 41.26875 --fov-deg 1.8 \ - --exposure 1e15 --output output/imgs/2mass_m31.png -./build/minkowski_sky --catalog assets/2mass/processed/2mass_psc_m44_1p0deg_stars.csv \ - --look-ra-deg 129.99165 --look-dec-deg 19.54139 --fov-deg 2.0 \ - --exposure 1e15 --width 1920 --height 1920 --output output/imgs/2mass_m44.png -./build/minkowski_sky --write-catalog assets/sky_grid_5deg.csv -``` - -The camera is a fixed inertial observer at coordinate position `(0,0,0)`, -with a tetrad whose forward direction is coordinate `-Z` and whose vertical -direction is `+Y`. The frame first triangulates the image plane, then traces -only its vertices backwards. Escaped endpoints form a triangulation on the -source sky. For every locally invertible triangle, catalog stars inside its -spherical source triangle are interpolated back to the image triangle and -splatted as PSFs. Consequently multiple image triangles naturally create -multiple images of the same star. - -The ray state evolves `(x^i, Pi_i, log(alpha p^0))` in coordinate time with -RK4 using the 3+1 equations in Bohn et al. II.A, until the spacetime backend -classifies the ray. `spacetime.c` is the only module containing the Minkowski -metric or its infinity criterion; frame, observer, and integrator use only -`SpacetimeSource` and `MetricData`. The initial regular mesh size is exposed -as `--coarse-cell-pixels`; it is a Phase-0 sampling knob, not a settled -production refinement threshold. - -### 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 short Schwarzschild diagnostic that permits at most one actual split -generation, for example: - -```sh -./build/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. - -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. - -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. - -To preserve a render for later exposure and tone-mapping work, build the desired -backend with `ENABLE_HDR=1`, for example `make SPACETIME=schwarzschild -ENABLE_HDR=1`. 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. - -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. +*4K test-grid reference image. Click to view at full resolution.* diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..9cdafc2 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,102 @@ +# GR 4D ray tracing + +[English](README.md) | **简体中文** + +一个以物理正确性为优先的离线时空渲染器。项目旨在通过沿四维时空向过去追踪光线,将双黑洞并合等随时间演化的数值相对论模拟渲染为 4K 视频。 + +[![Schwarzschild 黑洞对 2MASS 银心方向星场的引力透镜效果](assets/images/schwarzschild_galactic_center.png)](assets/images/schwarzschild_galactic_center.png) + +*从半径 100 M 处的相机观察 Schwarzschild 时空中的 2MASS 银心方向星场。点击图片查看完整 4K 图像;[渲染命令见下文](#示例schwarzschild-时空中的银心方向星场)。* + +恒星以独立的星表点源表示,保留方向、温度和振幅。渲染器将它们映射到相机图像中,处理多像、引力透镜放大和频移,再将保留亚像素位置的点扩散函数(PSF)累积到 HDR 图像。运动相机由世界线与四标架(tetrad)轨迹描述。 + +## 当前进展 + +当前实现支持解析 **Minkowski** 与 **Schwarzschild** 时空、单张图像、基于观测者轨迹的图像序列、自适应透镜网格,以及可复用的透镜映射文件。程序主要使用 C 编写,通过 OpenMP 实现 CPU 并行;可选的 HIP 后端用于加速 PSF 累积。 + +[Nmesh](https://github.com/nmeshsource/nmesh) 数值时空后端和双黑洞(BBH)渲染仍在规划中。当前范围为黑洞捕获与远处恒星背景,暂不包含局域物质辐射、吸积盘或等离子体。架构与开发路线参见[设计文档](nr_spacetime_movie_renderer_design.md)。 + +## 编译 + +默认构建需要支持 OpenMP 的 C11 编译器、GNU Make,以及 libpng 开发文件。在仓库根目录运行: + +```sh +make -j +``` + +这会同时生成 `build/Release/minkowski_sky` 和 `build/Release/schwarzschild_sky`。单独编译某个后端、Debug 构建、可选 HDR/FITS 输出、HIP 支持及回归检查,参见 [build.md](build.md)(英文)。 + +## 准备星表 + +渲染器支持**转换为规定 CSV 格式的任意星表**:四列依次为 ICRS 赤经、赤纬(单位均为度)、温度(单位为开尔文)和振幅。使用 `--catalog PATH` 加载单个 CSV 文件。 + +**2MASS 是本项目推荐的巡天星表**,项目提供配套的下载与处理脚本。具体操作参见 [assets/2mass/README.md](assets/2mass/README.md)(英文)。完整下载大约需要**三到四天**,因此建议**优先联系我获取压缩包**。下方示例使用的已处理星表分块应放在 `assets/2mass/processed/all_sky/`。 + +仓库提供的 [assets/sky_grid_5deg.csv](assets/sky_grid_5deg.csv) 是用于几何验证与回归测试的合成恒星网格。无需下载巡天数据,即可通过 `--catalog assets/sky_grid_5deg.csv` 使用,参见下方[测试网格示例](#示例叠加网格的合成测试星表)。 + +## 渲染示例 + +以下示例展示星区、时空和相机设置的几种组合。完成编译并准备好星表后,可以按需调整命令中的视场、观测者位置和渲染参数。可用的控制选项与工作流程参见 [usage.md](usage.md)(英文)。 + +### 示例:平直时空中的夏季大三角 + +这个示例渲染夏季大三角附近的星场: + +```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 \ + --output output/imgs/summer_triangle.png +``` + +[![平直时空中的夏季大三角,曝光为 1e12](assets/images/summer_triangle.png)](assets/images/summer_triangle.png) + +*曝光为 `1e12` 的 4K 参考图像。点击查看完整分辨率。* + +角度单位为度,`--fov-deg` 表示水平视场角。曝光是可调节的显示倍率。长时间渲染时可使用 `--verbose` 查看进度。 + +### 示例:Schwarzschild 时空中的银心方向星场 + +这个示例将相机放在半径 100 M 处,渲染本页顶部展示的、受到引力透镜作用的银心方向星场: + +```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 \ + --output output/imgs/schwarzschild_galactic_center.png +``` + +这里使用参考图像的渲染设置,包括 `--max-cache-psf-flux 1e8` 这一预览近似:它将明亮 PSF 的翼部截断在缓存半径内。[原始 benchmark 记录](benchmarks/2mass_galactic_center_blackhole.md)保留了命令与终端输出;上述命令省略了可选的 HDR 导出。 + +Schwarzschild 相机朝向黑洞。自适应细分默认关闭,应根据所需图像精度配置。相机控制、图像序列、透镜映射复用、PSF 设置及 HDR 输出参见 [usage.md](usage.md)(英文)。两个可执行文件都可通过 `--help` 查看完整选项。 + +### 示例:叠加网格的合成测试星表 + +这个示例使用 `assets/sky_grid_5deg.csv` 检查 Schwarzschild 时空中的引力透镜效果与自适应网格细分。`--draw-mesh` 会叠加显示最终的像平面三角网格。 + +```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 \ + --output output/imgs/schwarzschild_test_grid.png +``` + +[![Schwarzschild 黑洞对合成恒星网格的透镜效果,叠加自适应网格](assets/images/schwarzschild_test_grid.png)](assets/images/schwarzschild_test_grid.png) + +*4K 测试网格参考图像。点击查看完整分辨率。* diff --git a/assets/images/schwarzschild_galactic_center.png b/assets/images/schwarzschild_galactic_center.png new file mode 100644 index 0000000..57b79e1 Binary files /dev/null and b/assets/images/schwarzschild_galactic_center.png differ diff --git a/assets/images/schwarzschild_test_grid.png b/assets/images/schwarzschild_test_grid.png new file mode 100644 index 0000000..c046feb Binary files /dev/null and b/assets/images/schwarzschild_test_grid.png differ diff --git a/assets/images/summer_triangle.png b/assets/images/summer_triangle.png new file mode 100644 index 0000000..62455e3 Binary files /dev/null and b/assets/images/summer_triangle.png differ diff --git a/benchmarks/2mass_galactic_center_blackhole.md b/benchmarks/2mass_galactic_center_blackhole.md new file mode 100644 index 0000000..02c1654 --- /dev/null +++ b/benchmarks/2mass_galactic_center_blackhole.md @@ -0,0 +1,27 @@ +Git hash: dd8cf29dac557c881e5df1db15a58fae15294967 + +``` +❯ time ./build/Release/schwarzschild_sky \ + --psf-relative-tail 1e-8 \ + --psf-min-y 0 \ + --max-cache-psf-flux 1e8 \ + --all-sky-catalog assets/2mass/processed/all_sky \ + --refine-max-level 4 \ + --refine-jacobian-min 0.2 \ + --width 3840 --height 2160 \ + --look-ra-deg 262.5 --look-dec-deg -30 \ + --fov-deg 45 \ + --coarse-cell-pixels 16 \ + --exposure 1e13 \ + --psf-fwhm-pixels 2.7 --psf-moffat-beta 4.5 \ + --catalog-load-workers 4 \ + --observer-radius 100 \ + --hdr-output \ + --output output/imgs/2mass_galactic_center_schwarzschild_R100_4k_1e13_beta4.5_16_4-full.png +PSF cache ready: 64x64 phases, radius 47 px, relative tail 1e-08, tail abs 1e-06, boundary 1e-07, build 0.693 s + +Catalog prefetch: 64440 requested, 64440 newly loaded (343363141 stars), 0 unavailable in 48.016 s; 4 loader workers +Rendered 598308919 images from 343363141 catalog stars to output/imgs/2mass_galactic_center_schwarzschild_R100_4k_1e13_beta4.5_16_4-full.png (ok) +PSF splats: cached 598308919, cached wing-clipped 1365, direct fallbacks 0, discarded below min-Y 0 +./build/Release/schwarzschild_sky --psf-relative-tail 1e-8 --psf-min-y 0 1e8 21346.08s user 132.97s system 1520% cpu 23:32.47 total +``` diff --git a/build.md b/build.md new file mode 100644 index 0000000..7e1d69d --- /dev/null +++ b/build.md @@ -0,0 +1,122 @@ +# Build guide + +Run these commands from the repository root. The build uses GNU Make and +produces standalone executables under `build/Release/` or `build/Debug/`. + +## Dependencies + +The default CPU build needs: + +- A C11 compiler and OpenMP runtime (for example, GCC with libgomp). +- GNU Make. +- libpng headers and library for PNG output. + +Optional dependencies are CFITSIO and `pkg-config` for HDR/FITS output, and +HIP/ROCm with `hipcc` and a compatible GPU for HIP PSF accumulation. + +## CPU build + +```sh +make -j +``` + +With no explicit `SPACETIME` setting, this builds both supported spacetimes: + +| Executable | Spacetime | +| --- | --- | +| `build/Release/minkowski_sky` | Flat Minkowski spacetime | +| `build/Release/schwarzschild_sky` | Analytic Schwarzschild in ingoing Kerr–Schild coordinates | + +To build only one: + +```sh +make -j SPACETIME=minkowski backend +make -j SPACETIME=schwarzschild backend +``` + +Each executable contains one metric provider, selected at compile time. +`PSF_BACKEND=cpu` is the default. Release builds use `-O2 -DNDEBUG`; the +default compiler flags also include `-march=native`, targeting the build +machine's CPU. For a binary intended for other CPUs, override `CFLAGS`: + +```sh +make -B -j CFLAGS='-std=c11 -pipe -Wall -Wextra -Wpedantic' +``` + +For a Debug build (`-O0 -g3`): + +```sh +make -j BUILD_TYPE=Debug +``` + +Debug executables are placed in `build/Debug/`. + +## Output formats + +PNG is enabled by default. If libpng is unavailable, build with binary PPM +output instead and use a `.ppm` output filename: + +```sh +make -B -j ENABLE_PNG=0 +``` + +To enable the optional `--hdr-output` switch, install CFITSIO and `pkg-config`, +then build the desired spacetime with `ENABLE_HDR=1`: + +```sh +make -j SPACETIME=schwarzschild ENABLE_HDR=1 backend +``` + +The executable keeps its usual name, `build/Release/schwarzschild_sky`. +`--hdr-output` writes a linear RGB FITS file alongside the ordinary +single-frame image; see [usage.md](usage.md#hdr-output). Use `ENABLE_HDR=0` +to rebuild without this feature. + +## Optional HIP PSF acceleration + +```sh +make -j PSF_BACKEND=hip SPACETIME=minkowski backend +make -j PSF_BACKEND=hip SPACETIME=schwarzschild backend +``` + +These produce `build/Release/minkowski_sky_hip` and +`build/Release/schwarzschild_sky_hip`, separately from the CPU executables. +`HIPCC` can override the default `hipcc` command. `ENABLE_HDR=1` can be combined +with either HIP build. + +HIP accelerates cache-eligible PSF accumulation; ray tracing and catalog +mapping remain on the CPU. PSFs requiring direct evaluation retain the CPU +reference path with ordered HDR transfers. HIP initialization or execution +errors terminate the render instead of silently selecting the CPU backend. + +## Rebuilding and checks + +Build type, spacetime, HDR, and PSF backend configurations have separate object +paths. Changes to flags such as `ENABLE_PNG`, `CC`, or `CFLAGS` require a forced +rebuild with `make -B` and the intended settings. `make clean` removes the +entire `build/` directory; keep rendered images and other retained output +under `output/`. + +Inspect the available runtime options: + +```sh +./build/Release/minkowski_sky --help +./build/Release/schwarzschild_sky --help +``` + +Run CPU regression checks: + +```sh +make test +``` + +The HIP regression requires an actual compatible GPU. The Make target builds +the test executable; run it separately: + +```sh +make PSF_BACKEND=hip SPACETIME=minkowski hip-psf-test +./build/Release/test_hip_psf +``` + +For rendering with real stellar data, continue with [README.md](README.md#prepare-the-stellar-catalog) +and the [rendering guide](usage.md). diff --git a/usage.md b/usage.md new file mode 100644 index 0000000..cf52e23 --- /dev/null +++ b/usage.md @@ -0,0 +1,239 @@ +# 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.