// ---------------------------------------------------------------------------
// Line-pattern shader — textured line rendering with repeating pattern.
//
// Extends the solid line shader with a pattern texture sampled at per-vertex
// UVs. U maps along the line centreline, V maps across the line width.
// Retains dash-pattern evaluation, SDF cap/join AA, and horizon fog.
// ---------------------------------------------------------------------------
struct Uniforms {
view_proj: mat4x4<f32>,
fog_color: vec4<f32>,
eye_pos: vec4<f32>,
fog_params: vec4<f32>, // (start, end, density, 0)
line_style: vec4<f32>, // (dash_length, gap_length, cap_round, 0)
};
struct LinePatternVertexInput {
@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,
@location(5) uv: vec2<f32>,
};
struct LinePatternVertexOutput {
@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,
@location(5) uv: vec2<f32>,
};
@group(0) @binding(0)
var<uniform> u: Uniforms;
@group(1) @binding(0)
var pattern_texture: texture_2d<f32>;
@group(1) @binding(1)
var pattern_sampler: sampler;
@vertex
fn vs_main(in: LinePatternVertexInput) -> LinePatternVertexOutput {
var out: LinePatternVertexOutput;
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;
out.uv = in.uv;
return out;
}
@fragment
fn fs_main(in: LinePatternVertexOutput) -> @location(0) vec4<f32> {
// Sample the repeating pattern texture.
let pattern_color = textureSample(pattern_texture, pattern_sampler, in.uv);
// --- Dash pattern (optional) ---
let dash_len = u.line_style.x;
let gap_len = u.line_style.y;
let cycle = dash_len + gap_len;
var alpha = pattern_color.a;
if cycle > 0.0 {
let d = in.line_distance % cycle;
if d > dash_len {
discard;
}
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.
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.
let ribbon_aa = smoothstep(1.0, 0.95, edge_dist);
alpha *= ribbon_aa;
}
// --- Horizon fog ---
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(pattern_color.rgb, u.fog_color.rgb, fog_mix);
return vec4<f32>(blended_rgb, alpha * (1.0 - fog_mix));
}