rustial-renderer-wgpu 1.0.0

Pure WGPU renderer for the rustial 2.5D map engine
Documentation
// Dedicated line shader with dash-pattern, line-cap/join SDF AA, and fog.
//
// Line caps and joins are generated as tessellation geometry by the engine.
// Round cap/join fan vertices are placed at a circumscribed radius with
// normals slightly > 1.0.  The shader clips at SDF distance = 1.0 to
// produce pixel-perfect circular edges regardless of polygon segment count.
//
// The shader handles:
//   - dash-pattern evaluation using per-vertex centreline distance
//   - round-cap antialiasing at dash boundaries when cap_round = 1.0
//   - SDF circle AA for round cap/join fan geometry (cap_join flag)
//   - linear edge AA for ribbon body geometry
//   - horizon fog blending

struct Uniforms {
    view_proj:   mat4x4<f32>,
    fog_color:   vec4<f32>,
    eye_pos:     vec4<f32>,
    fog_params:  vec4<f32>,   // (start, end, density, 0)
    // Line-specific uniforms packed into a spare vec4:
    //   line_style.x = dash_length   (world-space meters)
    //   line_style.y = gap_length    (world-space meters)
    //   line_style.z = cap_round (1.0 = round, 0.0 = butt)
    //   line_style.w = 0 (reserved)
    line_style:  vec4<f32>,
};

struct LineVertexInput {
    @location(0) position:      vec3<f32>,
    @location(1) color:         vec4<f32>,
    @location(2) line_normal:   vec2<f32>,
    @location(3) line_distance: f32,
    @location(4) cap_join:      f32,
};

struct LineVertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) color:         vec4<f32>,
    @location(1) world_pos:     vec3<f32>,
    @location(2) line_distance: f32,
    @location(3) line_normal:   vec2<f32>,
    @location(4) cap_join:      f32,
};

@group(0) @binding(0)
var<uniform> u: Uniforms;

@vertex
fn vs_main(in: LineVertexInput) -> LineVertexOutput {
    var out: LineVertexOutput;
    out.clip_position = u.view_proj * vec4<f32>(in.position, 1.0);
    out.color = in.color;
    out.world_pos = in.position;
    out.line_distance = in.line_distance;
    out.line_normal = in.line_normal;
    out.cap_join = in.cap_join;
    return out;
}

@fragment
fn fs_main(in: LineVertexOutput) -> @location(0) vec4<f32> {
    // --- Dash pattern ---
    let dash_len = u.line_style.x;
    let gap_len  = u.line_style.y;
    let cap_round = u.line_style.z;
    let cycle = dash_len + gap_len;

    var alpha = in.color.a;

    // Only apply dash pattern when cycle > 0 (both dash and gap are positive).
    if cycle > 0.0 {
        let d = in.line_distance % cycle;
        if d > dash_len {
            // In the gap portion -- discard.
            discard;
        }
        // Soft anti-aliased edge at dash boundaries.
        let edge_aa = 0.5;
        let dash_edge = smoothstep(0.0, edge_aa, d) *
                        smoothstep(0.0, edge_aa, dash_len - d);
        alpha *= dash_edge;
    }

    // --- Edge antialiasing ---
    let edge_dist = length(in.line_normal);

    if in.cap_join > 0.5 {
        // SDF circle AA for round cap/join regions.
        // Fan perimeter vertices have normals with magnitude slightly > 1.0
        // (circumscribed polygon).  The SDF distance = length(normal) traces
        // a perfect circle at magnitude 1.0.  Discard fragments outside the
        // circle and apply smooth AA at the boundary.
        if edge_dist > 1.0 {
            discard;
        }
        let sdf_aa = smoothstep(1.0, 0.92, edge_dist);
        alpha *= sdf_aa;
    } else {
        // Linear edge AA for ribbon body.
        // The normal magnitude approaches 0 at the centreline and 1.0 at the
        // ribbon edge.  Use it for a smooth alpha falloff.
        let ribbon_aa = smoothstep(1.0, 0.95, edge_dist);
        alpha *= ribbon_aa;
    }

    // --- Horizon fog (same as vector.wgsl) ---
    let dx = in.world_pos.x - u.eye_pos.x;
    let dy = in.world_pos.y - u.eye_pos.y;
    let ground_dist = sqrt(dx * dx + dy * dy);
    let fog_start = u.fog_params.x;
    let fog_end   = u.fog_params.y;
    let density   = u.fog_params.z;
    let fog_t = clamp(
        (ground_dist - fog_start) / max(fog_end - fog_start, 0.001),
        0.0, 1.0,
    ) * density;
    let fog_mix = fog_t * 0.7;

    let blended_rgb = mix(in.color.rgb, u.fog_color.rgb, fog_mix);
    return vec4<f32>(blended_rgb, alpha * (1.0 - fog_mix));
}