kiss3d 0.41.0

Keep it simple, stupid, 2D and 3D graphics engine for Rust.
Documentation
// Polyline shader for thick line rendering
// Based on bevy_polyline (https://github.com/ForesightMiningSoftwareCorporation/bevy_polyline)
//
// Uses instanced rendering where each instance is a line segment.
// Material data (color, width, depth_bias) is passed per-instance via vertex attributes.
// Lines are drawn in world space (no model transform).

// View uniforms (bind group 0)
struct ViewUniforms {
    view: mat4x4<f32>,
    proj: mat4x4<f32>,
    viewport: vec4<f32>, // x, y, width, height
}

@group(0) @binding(0)
var<uniform> view: ViewUniforms;

// Vertex input - line segment endpoints with material data (per-instance)
struct VertexInput {
    @location(0) point_a: vec3<f32>,
    @location(1) width: f32,
    @location(2) point_b: vec3<f32>,
    @location(3) depth_bias: f32,
    @location(4) color: vec4<f32>,
    @location(5) perspective: u32,
    @builtin(vertex_index) index: u32,
}

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) color: vec4<f32>,
}

// Clip a point against the near plane
fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
    // Move a if a is behind the near plane and b is in front.
    if a.z > a.w && b.z <= b.w {
        // Interpolate a towards b until it's at the near plane.
        let distance_a = a.z - a.w;
        let distance_b = b.z - b.w;
        let t = distance_a / (distance_a - distance_b);
        return a + (b - a) * t;
    }
    return a;
}

@vertex
fn vs_main(vertex: VertexInput) -> VertexOutput {
    // 6 vertices per line segment forming 2 triangles
    // Positions encode: y = perpendicular offset (-0.5 or 0.5), z = along line (0 or 1)
    var positions = array<vec3<f32>, 6u>(
        vec3(0.0, -0.5, 0.0),
        vec3(0.0, -0.5, 1.0),
        vec3(0.0, 0.5, 1.0),
        vec3(0.0, -0.5, 0.0),
        vec3(0.0, 0.5, 1.0),
        vec3(0.0, 0.5, 0.0)
    );
    let position = positions[vertex.index % 6u];

    // Points are already in world space
    let world_a = vec4(vertex.point_a, 1.0);
    let world_b = vec4(vertex.point_b, 1.0);

    // Transform to clip space
    let view_proj = view.proj * view.view;
    var clip0 = view_proj * world_a;
    var clip1 = view_proj * world_b;

    // Manual near plane clipping to avoid errors when doing the perspective divide
    clip0 = clip_near_plane(clip0, clip1);
    clip1 = clip_near_plane(clip1, clip0);

    // Interpolate along the line based on position.z
    let clip = mix(clip0, clip1, position.z);

    // Calculate screen-space positions
    let resolution = vec2(view.viewport.z, view.viewport.w);
    let screen0 = resolution * (0.5 * clip0.xy / clip0.w + 0.5);
    let screen1 = resolution * (0.5 * clip1.xy / clip1.w + 0.5);

    // Calculate basis vectors for the line in screen space
    let line_dir = screen1 - screen0;
    let line_length = length(line_dir);

    // Handle degenerate case where points project to same location
    var x_basis: vec2<f32>;
    var y_basis: vec2<f32>;
    if line_length > 0.001 {
        x_basis = line_dir / line_length;
        y_basis = vec2(-x_basis.y, x_basis.x);
    } else {
        x_basis = vec2(1.0, 0.0);
        y_basis = vec2(0.0, 1.0);
    }

    var line_width = vertex.width;
    var color = vertex.color;

    // Perspective mode: width varies with distance (thinner when further away)
    if vertex.perspective != 0u {
        line_width = line_width / clip.w;
        // Line thinness fade for anti-aliasing when line becomes sub-pixel
        if line_width > 0.0 && line_width < 1.0 {
            color.a = color.a * line_width;
            line_width = 1.0;
        }
    }

    // Calculate offset from line center
    let pt_offset = line_width * (position.x * x_basis + position.y * y_basis);
    let pt0 = screen0 + pt_offset;
    let pt1 = screen1 + pt_offset;
    let pt = mix(pt0, pt1, position.z);

    // Apply depth bias
    var depth = clip.z;
    let depth_bias = vertex.depth_bias;
    if depth_bias >= 0.0 {
        depth = depth * (1.0 - depth_bias);
    } else {
        let epsilon = 4.88e-04;
        // Exponential depth bias for easier user control
        depth = depth * exp2(-depth_bias * log2(clip.w / depth - epsilon));
    }

    var out: VertexOutput;
    out.clip_position = vec4(clip.w * ((2.0 * pt) / resolution - 1.0), depth, clip.w);
    out.color = color;
    return out;
}

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