From 661d28a6a15e8881bcc463b82734e198ad2f0cff Mon Sep 17 00:00:00 2001 From: xfy Date: Mon, 13 Jul 2026 09:37:25 +0800 Subject: [PATCH] feat: lensed Flamm paraboloid curvature grid --- assets/shaders/black_hole.wgsl | 8 ++++++ assets/shaders/grid.wgsl | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 assets/shaders/grid.wgsl diff --git a/assets/shaders/black_hole.wgsl b/assets/shaders/black_hole.wgsl index c9f7a71..c5f6573 100644 --- a/assets/shaders/black_hole.wgsl +++ b/assets/shaders/black_hole.wgsl @@ -4,6 +4,7 @@ #import "shaders/stars.wgsl" #import "shaders/disk.wgsl" #import "shaders/planets.wgsl" +#import "shaders/grid.wgsl" struct BlackHoleUniforms { eye: vec4, @@ -87,6 +88,13 @@ fn fragment(in: VertexOutput) -> @location(0) vec4 { if (accum_alpha > 0.99) { break; } } + if (uniforms.grid_enabled != 0u) { + let g = grid_hit(prev, new_pos); + if (g.x + g.y + g.z > 0.0) { + accum_color += g; // additive + } + } + prev = new_pos; pos = new_pos; d = new_dir; diff --git a/assets/shaders/grid.wgsl b/assets/shaders/grid.wgsl new file mode 100644 index 0000000..5838102 --- /dev/null +++ b/assets/shaders/grid.wgsl @@ -0,0 +1,50 @@ +// Flamm's paraboloid embedding: z(r) = 2*sqrt(Rs*(r - Rs)), opens DOWNWARD +// (negative y in disk-local space). Dips below the disk toward the center — +// the classic gravity-well visualization. Traced through curved spacetime, so +// grid lines near the hole bend dramatically. + +fn flamm_depth(r: f32) -> f32 { + if (r <= uniforms.rs) { return 0.0; } + return -2.0 * sqrt(uniforms.rs * (r - uniforms.rs)); +} + +// Returns additive grid color if the segment prev->cur crosses the Flamm +// paraboloid surface; returns black otherwise. `prev`/`cur` are disk-local. +fn grid_hit(prev: vec3, cur: vec3) -> vec3 { + // Sample the paraboloid at the segment endpoints; if the segment crosses it, + // find an approximate crossing by sampling. + let r0 = length(vec2(prev.x, prev.z)); + let r1 = length(vec2(cur.x, cur.z)); + let z0_surf = flamm_depth(r0); + let z1_surf = flamm_depth(r1); + // Did the ray's y cross the surface y between endpoints? + if ((prev.y - z0_surf) * (cur.y - z1_surf) > 0.0) { + return vec3(0.0); + } + // Crossing: linear-search for the crossing point. + var hit = vec3(0.0); + var found = false; + for (var s: i32 = 0; s < 8; s = s + 1) { + let f = f32(s + 1) / 8.0; + let p = mix(prev, cur, vec3(f)); + let r = length(vec2(p.x, p.z)); + let surf = flamm_depth(r); + if (abs(p.y - surf) < 0.3) { + hit = p; + found = true; + break; + } + } + if (!found) { return vec3(0.0); } + + // Polar grid pattern from (r, phi). + let r = length(vec2(hit.x, hit.z)); + let phi = atan2(hit.z, hit.x); + let ring = smoothstep(0.06, 0.0, abs(fract(r * uniforms.grid_density * 0.5) - 0.5)); + let spoke = smoothstep(0.04, 0.0, abs(fract(phi * 6.0 / 6.283185) - 0.5)); + let grid = max(ring, spoke); + // Fade with depth so the grid reads as "below" the hole. + let fade = smoothstep(-15.0, -1.0, hit.y); + let col = vec3(0.15, 0.3, 0.6) * grid * fade; + return col * 0.5; // additive, low intensity +}