singularity-rs/assets/shaders/geodesic_schwarzschild.wgsl
xfy 8931904495 feat: lensed planet ray-sphere intersection in geodesic tracer
Add planets as ray-traced spheres inside the RK4 geodesic loop. A new
planets.wgsl shader tests each integrator segment against a storage
buffer of SphereData (center/radius/color/emissive) and composites hits
front-to-back alongside the accretion disk. Lambert shading with a fixed
light direction; emissive flag bypasses shading.

- assets/shaders/planets.wgsl: segment-sphere intersection + shading
- src/scene/: Planet component, upload_planets system, default planet
- black_hole.wgsl: wire planet_hit into the compositing loop
- geodesic_schwarzschild.wgsl: replace tuple return with Deriv struct
2026-07-10 17:06:19 +08:00

41 lines
1.3 KiB
WebGPU Shading Language

const R_ESCAPE: f32 = 1000.0;
struct Deriv {
dpos: vec3<f32>,
ddir: vec3<f32>,
}
fn deriv(pos: vec3<f32>, dir: vec3<f32>) -> Deriv {
let r = length(pos);
let rs = uniforms.rs;
let h = cross(pos, dir);
let h2 = dot(h, h);
let r5 = max(r * r * r * r * r, 1e-6);
let dpos = dir;
let accel = -1.5 * rs * h2 / r5 * pos;
return Deriv(dpos, accel);
}
struct RayResult {
status: u32,
final_pos: vec3<f32>,
final_dir: vec3<f32>,
}
fn classify_ray(start_pos: vec3<f32>, start_dir: vec3<f32>, steps: u32, dt: f32) -> RayResult {
var pos = start_pos;
var dir = start_dir;
for (var i: u32 = 0u; i < steps; i = i + 1u) {
let r = length(pos);
if (r < uniforms.rs) { return RayResult(1u, pos, dir); }
if (r > R_ESCAPE) { return RayResult(0u, pos, dir); }
let k1 = deriv(pos, dir);
let k2 = deriv(pos + k1.dpos * dt * 0.5, normalize(dir + k1.ddir * dt * 0.5));
let k3 = deriv(pos + k2.dpos * dt * 0.5, normalize(dir + k2.ddir * dt * 0.5));
let k4 = deriv(pos + k3.dpos * dt, normalize(dir + k3.ddir * dt));
pos = pos + (k1.dpos + 2.0 * k2.dpos + 2.0 * k3.dpos + k4.dpos) * dt / 6.0;
dir = normalize(dir + (k1.ddir + 2.0 * k2.ddir + 2.0 * k3.ddir + k4.ddir) * dt / 6.0);
}
return RayResult(0u, pos, dir);
}