From d32ea10aec0521f831a9d21d5c9bf11d87521192 Mon Sep 17 00:00:00 2001 From: xfy Date: Tue, 14 Jul 2026 23:24:14 +0800 Subject: [PATCH] docs: implementation plan for cinematic rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 10-task plan (Tasks 0-9) for the HDR bloom + FBM disk + ACES + AA pipeline. Each task is a standalone commit: Rgba16Float offscreen → FBM disk → gaussian-speck stars → brightpass → blur pyramid → composite+ACES → BloomQuality enum → Quality panel → verification. Conventions established upfront (camera orders, RenderLayers, the QuadScaleFactor/CompositeQuad/Nudgable markers) so each task references them rather than re-deriving. The naga var array fix for the blur kernel is baked into the shader from the start. --- ...026-07-14-blackhole-cinematic-rendering.md | 1584 +++++++++++++++++ 1 file changed, 1584 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-14-blackhole-cinematic-rendering.md diff --git a/docs/superpowers/plans/2026-07-14-blackhole-cinematic-rendering.md b/docs/superpowers/plans/2026-07-14-blackhole-cinematic-rendering.md new file mode 100644 index 0000000..c826ac7 --- /dev/null +++ b/docs/superpowers/plans/2026-07-14-blackhole-cinematic-rendering.md @@ -0,0 +1,1584 @@ +# Cinematic Rendering Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Raise the renderer from "geometrically correct but flat" to Gargantua-reference fidelity: HDR color + ACES tone mapping, multi-pass HDR bloom, domain-warped FBM disk texture, and anti-aliased round stars — all configurable via a dedicated Quality panel with tiered web defaults. + +**Architecture:** A 5-stage HDR pipeline replaces the 2-pass offscreen→upscale path: `Offscreen (Rgba16Float) → BrightPass → Down-pyramid → Up-pyramid → Composite+ACES`. The geodesic integrator is untouched; all changes are in compositing/shading. + +**Tech Stack:** Bevy 0.19, WGSL, egui 0.41. No new crate dependencies. + +**Spec:** `docs/superpowers/specs/2026-07-14-blackhole-cinematic-rendering-design.md` + +--- + +## Conventions used throughout this plan + +**Camera `order` is `i32`; lower renders first.** The full pipeline order, set once and referenced by all tasks: + +| Camera | order | Renders into | +|---------------|-------|-------------------| +| offscreen | -20 | offscreen_hdr | +| brightpass | -19 | bloom_0 | +| blur down01 | -18 | bloom_1 | +| blur down12 | -17 | bloom_2 | +| blur up21 | -16 | bloom_1 | +| blur up10 | -15 | bloom_final | +| composite | 0 | window | + +**RenderLayers** isolate each camera's draw to its own quad: layer 0 = offscreen, 1 = composite, 2 = brightpass, 3 = down01, 4 = down12, 5 = up21, 6 = up10. + +**`QuadScaleFactor(f32, f32)`** — a component on every quad storing the fraction of the offscreen resolution that the quad's target fills. Used by `resize_offscreen` to rescale each quad independently: +- offscreen quad: `(1.0, 1.0)` +- brightpass (writes bloom_0 at half-res): `(0.5, 0.5)` +- blur down01 (writes bloom_1 at quarter): `(0.25, 0.25)` +- blur down12 (writes bloom_2 at eighth): `(0.125, 0.125)` +- blur up21 (writes bloom_1 at quarter): `(0.25, 0.25)` +- blur up10 (writes bloom_final at half): `(0.5, 0.5)` +- composite quad: rescaled against the WINDOW (not offscreen), so it carries a `CompositeQuad` marker instead and is handled separately in resize. + +--- + +## File Structure + +**Created:** +- `assets/shaders/brightpass.wgsl` — stage [2]: extracts luminance > threshold with soft knee +- `assets/shaders/blur.wgsl` — stages [3]/[4]: one shader, down/up modes via uniform +- `assets/shaders/composite.wgsl` — stage [5]: ACES tone-map + bloom blend, replaces upscale.wgsl + +**Modified:** +- `assets/shaders/black_hole.wgsl` — FBM disk noise, gaussian-speck stars, `star_aa` branch, new uniform fields read +- `src/render/material.rs` — add `BrightPassMaterial`, `BlurMaterial`, `CompositeMaterial`; add new uniform fields to `BlackHoleUniforms`; remove `UpscaleMaterial` +- `src/render/plugin.rs` — spawn bloom targets/materials/cameras; `Nudgable` + `QuadScaleFactor` + `CompositeQuad` markers; generalize `nudge_camera`; bloom quality rebuild on param change +- `src/params.rs` — add `BloomQuality` enum + quality fields to `BlackHoleParams`; tiered defaults +- `src/ui.rs` — new `Quality` panel section + +**Deleted:** +- `assets/shaders/upscale.wgsl` + +--- + +## Task 0: Establish green baseline + +**Files:** none + +- [ ] **Step 1: Run the existing test suite** + +Run: `cargo test` +Expected: all physics tests pass (b_crit capture/escape, spin=0 degeneracy, spin>0 capture). + +- [ ] **Step 2: Confirm the desktop build runs** + +Run: `cargo build --release` +Expected: compiles without error. + +- [ ] **Step 3: No commit** — verification gate only. + +--- + +## Task 1: Offscreen format → Rgba16Float + camera order + markers + +**Files:** +- Modify: `src/render/plugin.rs` (offscreen texture format, offscreen camera order, add `Nudgable`/`QuadScaleFactor`/`CompositeQuad` markers to offscreen + upscale cameras/quads, generalize `nudge_camera`) + +This switches the offscreen render target to float, sets up the camera order for the bloom pipeline, and introduces the marker components all later tasks depend on. No bloom yet — the upscale path still works. + +- [ ] **Step 1: Define the new marker components** + +In `src/render/plugin.rs`, after the `UpscaleQuad` component definition (around line 34), add: + +```rust +/// Marker for any camera that must be nudged each frame (Bevy 0.19 #24448 +/// workaround). All render cameras carry this. +#[derive(Component)] +pub struct Nudgable; + +/// Stores the fraction of the offscreen resolution that this quad's target +/// fills. Used by resize_offscreen to rescale each quad independently. +/// (1.0, 1.0) = full offscreen res; (0.5, 0.5) = half; etc. +#[derive(Component)] +pub struct QuadScaleFactor(pub f32, pub f32); + +/// Marks the composite quad (renders to the window, not an offscreen Image). +/// resize_offscreen rescales it against the window, not the offscreen res. +#[derive(Component)] +pub struct CompositeQuad; +``` + +- [ ] **Step 2: Change the offscreen texture format in `spawn_fullscreen_quad`** + +In `src/render/plugin.rs`, find the `Image::new_target_texture` call in `spawn_fullscreen_quad` (around line 84) and change the format: + +```rust + let offscreen = images.add(Image::new_target_texture( + w, + h, + TextureFormat::Rgba16Float, // was Bgra8UnormSrgb — HDR for bloom headroom + None, + )); +``` + +- [ ] **Step 3: Add `QuadScaleFactor` to the offscreen quad** + +In `spawn_fullscreen_quad`, in the offscreen quad spawn (the `FullscreenQuad` `commands.spawn(...)`, around line 111), add `QuadScaleFactor(1.0, 1.0)` to the tuple: + +```rust + Transform::default().with_scale(Vec3::new(half_w, half_h, 1.0)), + FullscreenQuad, + QuadScaleFactor(1.0, 1.0), + RenderLayers::layer(0), +``` + +- [ ] **Step 4: Set the offscreen camera order to -20 and add `Nudgable`** + +In `spawn_fullscreen_quad`, in the offscreen camera spawn (the `commands.spawn((Camera2d, Camera { order: -1, ... }))` around line 119), change `order: -1` to `order: -20` and add `Nudgable,`: + +```rust + Camera { + order: -20, + clear_color: ClearColorConfig::Custom(Color::srgb(0.1, 0.1, 0.1)), + ..default() + }, + RenderTarget::Image(offscreen.clone().into()), + Msaa::Off, + OffscreenCamera, + Nudgable, + RenderLayers::layer(0), +``` + +- [ ] **Step 5: Tag the upscale quad + camera with `CompositeQuad` + `Nudgable`** + +In `spawn_fullscreen_quad`, in the upscale quad spawn (around line 133), add `CompositeQuad,`: + +```rust + Transform::default().with_scale(Vec3::new(win.width() / 2.0, win.height() / 2.0, 1.0)), + UpscaleQuad, + CompositeQuad, + RenderLayers::layer(1), +``` + +In the upscale camera spawn (around line 142), add `Nudgable,`: + +```rust + commands.spawn((Camera2d, Msaa::Off, UpscaleCamera, Nudgable, RenderLayers::layer(1))); +``` + +- [ ] **Step 6: Generalize `nudge_camera` to use the `Nudgable` marker** + +In `src/render/plugin.rs`, replace the `nudge_camera` function signature and query: + +```rust +fn nudge_camera( + time: Res