diff --git a/assets/shaders/black_hole.wgsl b/assets/shaders/black_hole.wgsl index c5f6573..b10395f 100644 --- a/assets/shaders/black_hole.wgsl +++ b/assets/shaders/black_hole.wgsl @@ -1,10 +1,13 @@ #import bevy_sprite::mesh2d_vertex_output::VertexOutput -#import "shaders/ray_gen.wgsl" -#import "shaders/geodesic_schwarzschild.wgsl" -#import "shaders/stars.wgsl" -#import "shaders/disk.wgsl" -#import "shaders/planets.wgsl" -#import "shaders/grid.wgsl" +// Bevy/naga_oil imports: each module file uses #define_import_path, then we +// import individual symbols via `namespace::name` (or `namespace::{a, b}`). +// Whole-file imports without `::` do NOT reliably bring functions into scope. +#import singularity::ray_gen::ray_direction +#import singularity::geodesic::{deriv, classify_ray} +#import singularity::stars::{hash13, star_color} +#import singularity::disk::{rot_x, disk_hit, disk_color} +#import singularity::planets::{SphereData, planets, planet_hit} +#import singularity::grid::{flamm_depth, grid_hit} struct BlackHoleUniforms { eye: vec4, @@ -41,12 +44,15 @@ fn fragment(in: VertexOutput) -> @location(0) vec4 { uv.x *= aspect; let dir = ray_direction(uv); + // Work in disk-local space: rotate eye + dir by -disk_tilt around X so the + // disk lies on y=0. (disk_hit/disk_color assume disk-local coords.) var pos = rot_x(uniforms.eye.xyz, -uniforms.disk_tilt); var d = normalize(rot_x(dir, -uniforms.disk_tilt)); let dt = max(length(uniforms.eye.xyz), 20.0) / f32(uniforms.steps); let steps = uniforms.steps; + // Front-to-back compositing. var accum_color = vec3(0.0); var accum_alpha = 0.0; @@ -54,9 +60,12 @@ fn fragment(in: VertexOutput) -> @location(0) vec4 { for (var i: u32 = 0u; i < steps; i = i + 1u) { let r = length(pos); if (r < uniforms.rs) { + // Captured: whatever we've composited so far is the result. break; } if (r > 1000.0) { + // Escaped: add background stars along the (disk-local) final dir. + // Rotate back to world for the star sample. let world_dir = normalize(rot_x(d, uniforms.disk_tilt)); let star = star_color(world_dir, uniforms.star_intensity); accum_color += (1.0 - accum_alpha) * star; @@ -64,6 +73,7 @@ fn fragment(in: VertexOutput) -> @location(0) vec4 { break; } + // RK4 step (single step), then test disk crossing on the segment. let k1 = deriv(pos, d); let k2 = deriv(pos + k1.dpos * dt * 0.5, normalize(d + k1.ddir * dt * 0.5)); let k3 = deriv(pos + k2.dpos * dt * 0.5, normalize(d + k2.ddir * dt * 0.5)); @@ -72,10 +82,11 @@ fn fragment(in: VertexOutput) -> @location(0) vec4 { let new_dir = normalize(d + (k1.ddir + 2.0*k2.ddir + 2.0*k3.ddir + k4.ddir) * dt / 6.0); if (disk_hit(prev, new_pos)) { + // Approximate the crossing point by interpolating to y=0. let ty = prev.y / (prev.y - new_pos.y); let hit = mix(prev, new_pos, vec3(ty)); let dc = disk_color(hit, new_dir); - let a = 0.85; + let a = 0.85; // disk is nearly opaque accum_color += (1.0 - accum_alpha) * dc * a; accum_alpha += (1.0 - accum_alpha) * a; if (accum_alpha > 0.99) { break; } diff --git a/assets/shaders/disk.wgsl b/assets/shaders/disk.wgsl index 330db3c..4f2f68d 100644 --- a/assets/shaders/disk.wgsl +++ b/assets/shaders/disk.wgsl @@ -1,5 +1,6 @@ // Disk plane is the xz-plane in world space, tilted by `disk_tilt` around the // x-axis. We work in "disk-local" coordinates by rotating the ray. +#define_import_path singularity::disk // Rotate a vector around the X axis by angle a. fn rot_x(v: vec3, a: f32) -> vec3 { diff --git a/assets/shaders/geodesic_schwarzschild.wgsl b/assets/shaders/geodesic_schwarzschild.wgsl index 8553aa9..799899a 100644 --- a/assets/shaders/geodesic_schwarzschild.wgsl +++ b/assets/shaders/geodesic_schwarzschild.wgsl @@ -1,3 +1,5 @@ +#define_import_path singularity::geodesic + const R_ESCAPE: f32 = 1000.0; struct Deriv { diff --git a/assets/shaders/grid.wgsl b/assets/shaders/grid.wgsl index 5838102..b415eb8 100644 --- a/assets/shaders/grid.wgsl +++ b/assets/shaders/grid.wgsl @@ -2,6 +2,7 @@ // (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. +#define_import_path singularity::grid fn flamm_depth(r: f32) -> f32 { if (r <= uniforms.rs) { return 0.0; } diff --git a/assets/shaders/planets.wgsl b/assets/shaders/planets.wgsl index 7d05b52..df2059d 100644 --- a/assets/shaders/planets.wgsl +++ b/assets/shaders/planets.wgsl @@ -1,8 +1,12 @@ +#define_import_path singularity::planets + struct SphereData { center: vec4, // xyz = center, w = radius color: vec4, // xyz = color, w = emissive flag (u32 reinterpreted; we just check > 0.5) }; +// The storage binding is declared here as part of the planets module; it lives +// in the material's bind group (group 2 = #{MATERIAL_BIND_GROUP}). @group(#{MATERIAL_BIND_GROUP}) @binding(3) var planets: array; // Test the segment prev->cur against all planets. Returns hit color & alpha, diff --git a/assets/shaders/ray_gen.wgsl b/assets/shaders/ray_gen.wgsl index a29f4fc..e4f0713 100644 --- a/assets/shaders/ray_gen.wgsl +++ b/assets/shaders/ray_gen.wgsl @@ -1,5 +1,7 @@ // Builds a world-space camera ray direction for the current pixel. // `uv` is the pixel coordinate normalized to [-1,1] with aspect correction. +#define_import_path singularity::ray_gen + fn ray_direction(uv: vec2) -> vec3 { // NOTE: `fov` is packed into the `.w` of `up` in BlackHoleUniforms // (the Rust struct lays out `up: Vec3` + `fov: f32` as one vec4 block). diff --git a/assets/shaders/stars.wgsl b/assets/shaders/stars.wgsl index 2690777..068fb39 100644 --- a/assets/shaders/stars.wgsl +++ b/assets/shaders/stars.wgsl @@ -1,4 +1,6 @@ // Hash-based procedural stars on the unit sphere. Returns RGB radiance. +#define_import_path singularity::stars + fn hash13(p: vec3) -> f32 { var q = vec3(dot(p, vec3(127.1, 311.7, 74.7)), dot(p, vec3(269.5, 183.3, 246.1)),