rsplot 0.5.1

silx-style scientific plotting for egui, rendered with wgpu
Documentation
// Volume ray-caster — front-to-back alpha compositing over a 3D RGBA texture.
//
// A full-screen triangle generates one camera ray per pixel from the inverse
// view-projection matrix. Each ray is clipped to the volume's axis-aligned box
// and marched in fixed steps, sampling straight-alpha RGBA and accumulating
// PREMULTIPLIED colour so the result blends with `PREMULTIPLIED_ALPHA_BLENDING`
// straight into egui's render pass (no offscreen target, no depth).

struct Uniforms {
    // Inverse of the camera clip matrix P·V (OpenGL clip, z in [-1, 1]).
    inv_mvp: mat4x4<f32>,
    cam_pos: vec4<f32>, // world-space camera position (xyz; w unused)
    vol_min: vec4<f32>, // world-space AABB min (xyz)
    vol_max: vec4<f32>, // world-space AABB max (xyz)
    // x = step count, y = alpha scale, z = sample-alpha cull floor, w unused.
    params: vec4<f32>,
};

@group(0) @binding(0) var<uniform> u: Uniforms;
@group(0) @binding(1) var vol_tex: texture_3d<f32>;
@group(0) @binding(2) var vol_samp: sampler;

struct VsOut {
    @builtin(position) clip: vec4<f32>,
    @location(0) ndc: vec2<f32>,
};

@vertex
fn vs_main(@builtin(vertex_index) vid: u32) -> VsOut {
    // Oversized triangle covering the NDC square [-1, 1]^2.
    var xy = array<vec2<f32>, 3>(
        vec2<f32>(-1.0, -1.0),
        vec2<f32>(3.0, -1.0),
        vec2<f32>(-1.0, 3.0),
    );
    let p = xy[vid];
    var out: VsOut;
    out.clip = vec4<f32>(p, 0.0, 1.0);
    out.ndc = p;
    return out;
}

// Ray / AABB slab test. Returns (t_near, t_far); a miss has t_far < t_near.
fn intersect_box(ro: vec3<f32>, rd: vec3<f32>, bmin: vec3<f32>, bmax: vec3<f32>) -> vec2<f32> {
    let inv = 1.0 / rd;
    let t0 = (bmin - ro) * inv;
    let t1 = (bmax - ro) * inv;
    let tsmall = min(t0, t1);
    let tbig = max(t0, t1);
    let tnear = max(max(tsmall.x, tsmall.y), tsmall.z);
    let tfar = min(min(tbig.x, tbig.y), tbig.z);
    return vec2<f32>(tnear, tfar);
}

@fragment
fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
    // Un-project the near/far NDC points to build the world-space ray.
    let near_h = u.inv_mvp * vec4<f32>(in.ndc, -1.0, 1.0);
    let far_h = u.inv_mvp * vec4<f32>(in.ndc, 1.0, 1.0);
    let near_w = near_h.xyz / near_h.w;
    let far_w = far_h.xyz / far_h.w;
    let ro = near_w;
    let rd = normalize(far_w - near_w);

    let hit = intersect_box(ro, rd, u.vol_min.xyz, u.vol_max.xyz);
    let tnear = max(hit.x, 0.0);
    let tfar = hit.y;
    if tfar <= tnear {
        discard;
    }

    let steps = max(i32(u.params.x), 1);
    let alpha_scale = u.params.y;
    let cull_floor = u.params.z;
    let extent = u.vol_max.xyz - u.vol_min.xyz;
    let dt = (tfar - tnear) / f32(steps);

    var acc = vec4<f32>(0.0, 0.0, 0.0, 0.0); // premultiplied rgb + alpha
    var t = tnear + dt * 0.5;
    for (var i = 0; i < steps; i = i + 1) {
        let p = ro + rd * t;
        let uvw = (p - u.vol_min.xyz) / extent;
        let s = textureSampleLevel(vol_tex, vol_samp, uvw, 0.0);
        if s.a > cull_floor {
            let a = clamp(s.a * alpha_scale, 0.0, 1.0);
            let w = (1.0 - acc.a) * a;
            acc = vec4<f32>(acc.rgb + s.rgb * w, acc.a + w);
        }
        if acc.a >= 0.995 {
            break;
        }
        t = t + dt;
    }
    return acc; // premultiplied, for PREMULTIPLIED_ALPHA_BLENDING
}