rsplot 0.5.0

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, specular (1,1,1) gated on a per-viewport shininess — 0 (off) for
// `Plot3DWidget`/`SceneWidget`, 32 for `ScalarFieldView`
// (`ScalarFieldView.py:928` `viewport.light.shininess = 32`). The shading
// 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.

struct MeshParams {
    // Clip-space MVP (proj × view × model), depth-corrected (as scene3d.wgsl).
    mvp: mat4x4<f32>,
    // Camera-space transform: the view matrix (model is identity — items bake
    // world-space vertices). For normals (w = 0, translation dropped) the rigid
    // view's 3×3 is its own inverse-transpose; for positions (w = 1) it yields
    // the camera-space vertex used by the specular and fog terms.
    normal_mat: mat4x4<f32>,
    // Linear fog datum (see scene3d.wgsl): x = scale, y = near, z = on/off.
    fog_info: vec4<f32>,
    fog_color: vec4<f32>,
    // x = Phong shininess exponent (0 disables specular, the silx
    // DirectionalLight default, function.py:296-300); yzw unused.
    light: vec4<f32>,
};

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

// silx viewport DirectionalLight defaults, camera frame (viewport.py:227-233
// direction (0,0,-1), ambient 0.3, diffuse 0.7; specular defaults to (1,1,1)).
const LIGHT_DIR: vec3<f32> = vec3<f32>(0.0, 0.0, -1.0);
const AMBIENT: f32 = 0.3;
const DIFFUSE: f32 = 0.7;
const SPECULAR: vec3<f32> = vec3<f32>(1.0, 1.0, 1.0);

struct VsOut {
    @builtin(position) clip: vec4<f32>,
    @location(0) color: vec4<f32>,
    @location(1) normal_cam: vec3<f32>,
    // Camera-space vertex position: silx's `viewPos - position` view vector
    // (the camera sits at the origin of this frame) and the fog's z datum.
    @location(2) pos_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.pos_cam = (params.normal_mat * vec4<f32>(pos, 1.0)).xyz;
    out.color = color;
    return out;
}

// Linear fog, port of silx scene/function.py Fog._fragDecl (:79-93); colours
// are premultiplied, so the fog colour is scaled by alpha (see scene3d.wgsl).
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> {
    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));

    // Specular, gated exactly as silx DirectionalLight (function.py:263-275):
    // shininess > 0 and the face lit. In the camera frame the view position is
    // the origin, so viewDir = normalize(-pos_cam).
    var spec_factor = 0.0;
    if (params.light.x > 0.0 && n_dot_l > 0.0) {
        let reflection = reflect(LIGHT_DIR, n);
        let view_dir = normalize(-in.pos_cam);
        spec_factor = max(0.0, dot(reflection, view_dir));
        if (spec_factor > 0.0) {
            spec_factor = pow(spec_factor, params.light.x);
        }
    }

    // silx: color.rgb * (ambient + diffuse·nDotL) + specular·specFactor, alpha
    // untouched. `color` is linear premultiplied, so the additive specular term
    // is scaled by alpha to keep the result premultiplied.
    let factor = AMBIENT + DIFFUSE * n_dot_l;
    let lit = vec4<f32>(
        in.color.rgb * factor + SPECULAR * spec_factor * in.color.a,
        in.color.a,
    );
    return apply_fog(lit, in.pos_cam.z);
}