rsplot 0.5.0

silx-style scientific plotting for egui, rendered with wgpu
Documentation
// 3D scene geometry shader (plot3d P0.2): lines and triangles, depth-tested,
// rendered into an offscreen color+depth target before being blitted into
// egui's (depth-less) pass.
//
// The MVP is `camera.matrix() × model`, already transposed to column-major and
// clip-corrected for wgpu's z∈[0,1] depth range (see `Mat4::to_gpu_clip_cols`);
// it arrives in the group(0) binding(0) uniform. Per-vertex position and color
// (linear, premultiplied) come from the vertex buffer.
//
// NOTE: `Params` is the shared group(0) uniform of the line/triangle pipelines
// AND the textured-image pipeline (`scene3d_image.wgsl`) — both bind the same
// buffer, so the two WGSL structs must stay layout-identical.

struct Params {
    mvp: mat4x4<f32>,
    // Linear fog datum, silx scene/function.py Fog (:79-151):
    // x = 0.9/(far-near) or 0, y = near (camera-space z), z = on/off, w unused.
    fog_info: vec4<f32>,
    // Fog colour = viewport background rgb (function.py:148-151); w unused.
    fog_color: vec4<f32>,
    // Row 2 of the view matrix: dot(view_row_z, vec4(pos, 1)) is the vertex's
    // camera-space z — silx's `vCameraPosition.z` fed to `fog()`.
    view_row_z: vec4<f32>,
};

@group(0) @binding(0)
var<uniform> params: Params;

struct VsOut {
    @builtin(position) clip: vec4<f32>,
    @location(0) color: vec4<f32>,
    // Camera-space z for the fog term (linear in position, so interpolation is
    // exact — matches silx's per-fragment `vCameraPosition`).
    @location(1) cam_z: f32,
};

@vertex
fn vs_main(
    @location(0) pos: vec3<f32>,
    @location(1) color: vec4<f32>,
) -> VsOut {
    var out: VsOut;
    out.clip = params.mvp * vec4<f32>(pos, 1.0);
    out.color = color;
    out.cam_z = dot(params.view_row_z, vec4<f32>(pos, 1.0));
    return out;
}

// Linear fog, port of silx scene/function.py Fog._fragDecl (:79-93):
// factor = clamp(fogExtentInfo.x * (cameraPos.z - fogExtentInfo.y), 0, 1);
// rgb = mix(color.rgb, fogColor, factor), alpha untouched. Colours here are
// premultiplied, so the fog colour is scaled by alpha to stay premultiplied.
fn apply_fog(color: vec4<f32>, cam_z: f32) -> vec4<f32> {
    if (params.fog_info.z == 0.0) {
        return color;
    }
    let factor = clamp(params.fog_info.x * (cam_z - params.fog_info.y), 0.0, 1.0);
    let rgb = mix(color.rgb, params.fog_color.rgb * color.a, factor);
    return vec4<f32>(rgb, color.a);
}

@fragment
fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
    return apply_fog(in.color, in.cam_z);
}