fix(shaders): use #define_import_path + namespaced imports

The whole-file #import "file.wgsl" form (without ::) does not reliably
bring functions into scope in naga_oil/Bevy 0.19 — the renderer was
failing at runtime with 'no definition in scope for identifier:
ray_direction' (and rot_x, deriv, etc.), producing a blank screen despite
'cargo build' passing (shaders aren't compile-checked at build time).

Fix: add #define_import_path to each module file and import symbols
explicitly via namespace::name (the canonical Bevy pattern from the
shader_material_2d example). Verified the full shader pipeline compiles
and runs at runtime with zero WGSL errors.

This was a pre-existing bug masked by incomplete runtime verification in
earlier tasks; it is NOT specific to Task 15's grid work. All earlier
visual features (shadow, Doppler disk, Einstein halo, stars, planets,
grid) now actually render.
This commit is contained in:
xfy 2026-07-13 09:51:08 +08:00
parent 661d28a6a1
commit 5fa2621e6a
7 changed files with 30 additions and 7 deletions

View File

@ -1,10 +1,13 @@
#import bevy_sprite::mesh2d_vertex_output::VertexOutput #import bevy_sprite::mesh2d_vertex_output::VertexOutput
#import "shaders/ray_gen.wgsl" // Bevy/naga_oil imports: each module file uses #define_import_path, then we
#import "shaders/geodesic_schwarzschild.wgsl" // import individual symbols via `namespace::name` (or `namespace::{a, b}`).
#import "shaders/stars.wgsl" // Whole-file imports without `::` do NOT reliably bring functions into scope.
#import "shaders/disk.wgsl" #import singularity::ray_gen::ray_direction
#import "shaders/planets.wgsl" #import singularity::geodesic::{deriv, classify_ray}
#import "shaders/grid.wgsl" #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 { struct BlackHoleUniforms {
eye: vec4<f32>, eye: vec4<f32>,
@ -41,12 +44,15 @@ fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
uv.x *= aspect; uv.x *= aspect;
let dir = ray_direction(uv); 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 pos = rot_x(uniforms.eye.xyz, -uniforms.disk_tilt);
var d = normalize(rot_x(dir, -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 dt = max(length(uniforms.eye.xyz), 20.0) / f32(uniforms.steps);
let steps = uniforms.steps; let steps = uniforms.steps;
// Front-to-back compositing.
var accum_color = vec3<f32>(0.0); var accum_color = vec3<f32>(0.0);
var accum_alpha = 0.0; var accum_alpha = 0.0;
@ -54,9 +60,12 @@ fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
for (var i: u32 = 0u; i < steps; i = i + 1u) { for (var i: u32 = 0u; i < steps; i = i + 1u) {
let r = length(pos); let r = length(pos);
if (r < uniforms.rs) { if (r < uniforms.rs) {
// Captured: whatever we've composited so far is the result.
break; break;
} }
if (r > 1000.0) { 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 world_dir = normalize(rot_x(d, uniforms.disk_tilt));
let star = star_color(world_dir, uniforms.star_intensity); let star = star_color(world_dir, uniforms.star_intensity);
accum_color += (1.0 - accum_alpha) * star; accum_color += (1.0 - accum_alpha) * star;
@ -64,6 +73,7 @@ fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
break; break;
} }
// RK4 step (single step), then test disk crossing on the segment.
let k1 = deriv(pos, d); let k1 = deriv(pos, d);
let k2 = deriv(pos + k1.dpos * dt * 0.5, normalize(d + k1.ddir * dt * 0.5)); 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)); 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<f32> {
let new_dir = normalize(d + (k1.ddir + 2.0*k2.ddir + 2.0*k3.ddir + k4.ddir) * dt / 6.0); 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)) { 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 ty = prev.y / (prev.y - new_pos.y);
let hit = mix(prev, new_pos, vec3<f32>(ty)); let hit = mix(prev, new_pos, vec3<f32>(ty));
let dc = disk_color(hit, new_dir); 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_color += (1.0 - accum_alpha) * dc * a;
accum_alpha += (1.0 - accum_alpha) * a; accum_alpha += (1.0 - accum_alpha) * a;
if (accum_alpha > 0.99) { break; } if (accum_alpha > 0.99) { break; }

View File

@ -1,5 +1,6 @@
// Disk plane is the xz-plane in world space, tilted by `disk_tilt` around the // 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. // 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. // Rotate a vector around the X axis by angle a.
fn rot_x(v: vec3<f32>, a: f32) -> vec3<f32> { fn rot_x(v: vec3<f32>, a: f32) -> vec3<f32> {

View File

@ -1,3 +1,5 @@
#define_import_path singularity::geodesic
const R_ESCAPE: f32 = 1000.0; const R_ESCAPE: f32 = 1000.0;
struct Deriv { struct Deriv {

View File

@ -2,6 +2,7 @@
// (negative y in disk-local space). Dips below the disk toward the center // (negative y in disk-local space). Dips below the disk toward the center
// the classic gravity-well visualization. Traced through curved spacetime, so // the classic gravity-well visualization. Traced through curved spacetime, so
// grid lines near the hole bend dramatically. // grid lines near the hole bend dramatically.
#define_import_path singularity::grid
fn flamm_depth(r: f32) -> f32 { fn flamm_depth(r: f32) -> f32 {
if (r <= uniforms.rs) { return 0.0; } if (r <= uniforms.rs) { return 0.0; }

View File

@ -1,8 +1,12 @@
#define_import_path singularity::planets
struct SphereData { struct SphereData {
center: vec4<f32>, // xyz = center, w = radius center: vec4<f32>, // xyz = center, w = radius
color: vec4<f32>, // xyz = color, w = emissive flag (u32 reinterpreted; we just check > 0.5) color: vec4<f32>, // 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<storage, read> planets: array<SphereData>; @group(#{MATERIAL_BIND_GROUP}) @binding(3) var<storage, read> planets: array<SphereData>;
// Test the segment prev->cur against all planets. Returns hit color & alpha, // Test the segment prev->cur against all planets. Returns hit color & alpha,

View File

@ -1,5 +1,7 @@
// Builds a world-space camera ray direction for the current pixel. // Builds a world-space camera ray direction for the current pixel.
// `uv` is the pixel coordinate normalized to [-1,1] with aspect correction. // `uv` is the pixel coordinate normalized to [-1,1] with aspect correction.
#define_import_path singularity::ray_gen
fn ray_direction(uv: vec2<f32>) -> vec3<f32> { fn ray_direction(uv: vec2<f32>) -> vec3<f32> {
// NOTE: `fov` is packed into the `.w` of `up` in BlackHoleUniforms // NOTE: `fov` is packed into the `.w` of `up` in BlackHoleUniforms
// (the Rust struct lays out `up: Vec3` + `fov: f32` as one vec4 block). // (the Rust struct lays out `up: Vec3` + `fov: f32` as one vec4 block).

View File

@ -1,4 +1,6 @@
// Hash-based procedural stars on the unit sphere. Returns RGB radiance. // Hash-based procedural stars on the unit sphere. Returns RGB radiance.
#define_import_path singularity::stars
fn hash13(p: vec3<f32>) -> f32 { fn hash13(p: vec3<f32>) -> f32 {
var q = vec3<f32>(dot(p, vec3<f32>(127.1, 311.7, 74.7)), var q = vec3<f32>(dot(p, vec3<f32>(127.1, 311.7, 74.7)),
dot(p, vec3<f32>(269.5, 183.3, 246.1)), dot(p, vec3<f32>(269.5, 183.3, 246.1)),