siplot 0.4.1

silx-style scientific plotting for egui, rendered with wgpu
Documentation
// Shaded triangle-mesh shader — the wgpu analogue of silx
// `scene/primitives.py` `Mesh3D` lit by `scene/function.py` `DirectionalLight`.
//
// silx shades plot3d meshes with a *headlight*: a directional Phong light fixed
// in camera space pointing into the screen (direction (0,0,-1)), ambient 0.3,
// diffuse 0.7, no specular (shininess 0) — the `Plot3DWidget` defaults. The
// shading therefore follows the camera as the scene is orbited, so it must be
// computed per-frame on the GPU. The normal is carried into camera space by the
// view matrix (`normal_mat`); positions are projected by the usual clip MVP.
//
// The light parameters are silx's viewport defaults, baked in as constants here
// (a lighting on/off / parameter API is a later enhancement).

struct MeshParams {
    // Clip-space MVP (proj × view × model), depth-corrected (as scene3d.wgsl).
    mvp: mat4x4<f32>,
    // Camera-space transform for normals: the view matrix (model is identity —
    // items bake world-space vertices — and the view is rigid, so its 3×3 is its
    // own inverse-transpose; w = 0 drops the translation).
    normal_mat: mat4x4<f32>,
};

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

// silx Plot3DWidget DirectionalLight defaults (camera space).
const LIGHT_DIR: vec3<f32> = vec3<f32>(0.0, 0.0, -1.0);
const AMBIENT: f32 = 0.3;
const DIFFUSE: f32 = 0.7;

struct VsOut {
    @builtin(position) clip: vec4<f32>,
    @location(0) color: vec4<f32>,
    @location(1) normal_cam: vec3<f32>,
};

@vertex
fn vs_main(
    @location(0) pos: vec3<f32>,
    @location(1) color: vec4<f32>,
    @location(2) normal: vec3<f32>,
) -> VsOut {
    var out: VsOut;
    out.clip = params.mvp * vec4<f32>(pos, 1.0);
    out.normal_cam = (params.normal_mat * vec4<f32>(normal, 0.0)).xyz;
    out.color = color;
    return out;
}

@fragment
fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
    let n = normalize(in.normal_cam);
    // One-sided Lambert term (silx `max(0.0, dot(normal, -lightDir))`).
    let n_dot_l = max(0.0, dot(n, -LIGHT_DIR));
    let factor = AMBIENT + DIFFUSE * n_dot_l;
    // `color` is linear, premultiplied alpha; scaling rgb by the lighting factor
    // keeps it premultiplied (alpha unchanged), as silx leaves color.a untouched.
    return vec4<f32>(in.color.rgb * factor, in.color.a);
}