add a shader for a newline track
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
struct Material {
|
||||
color_a: vec4<f32>,
|
||||
color_b: vec4<f32>,
|
||||
border_color: vec4<f32>,
|
||||
time: f32,
|
||||
speed: f32,
|
||||
border_size: f32,
|
||||
};
|
||||
|
||||
@group(2) @binding(0)
|
||||
var<uniform> mat: Material;
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let uv = in.uv;
|
||||
|
||||
// animate
|
||||
let x = uv.x + params.time * params.speed;
|
||||
let g = fract(x);
|
||||
|
||||
let fill = mix(params.color_a, params.color_b, g);
|
||||
|
||||
// borders
|
||||
let top = step(uv.y, params.border_size);
|
||||
let bottom = step(1.0 - params.border_size, uv.y);
|
||||
let border_mask = max(top, bottom);
|
||||
|
||||
return mix(fill, params.border_color, border_mask);
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
const PI: f32 = 3.14159265358979323846264338327950288;
|
||||
|
||||
struct Uniforms {
|
||||
/// The size of the canvas
|
||||
size: vec2<f32>,
|
||||
|
||||
/// The length of the first horizontal segment
|
||||
len_start: f32,
|
||||
/// The length of the last horizontal segment
|
||||
len_end: f32,
|
||||
|
||||
/// The inner half thickness
|
||||
thickness: f32,
|
||||
/// The thickness of the border (added to the inner thickness for total thickness)
|
||||
border: f32,
|
||||
|
||||
/// Current time
|
||||
time: f32,
|
||||
/// The period of the pulse effect (in seconds)
|
||||
pulse_period: f32,
|
||||
/// The speed at which the pulse travels along the path (in units per second)
|
||||
pulse_speed: f32,
|
||||
/// The width of the pulse effect (in units)
|
||||
pulse_width: f32,
|
||||
|
||||
/// The color of the border
|
||||
border_color: vec4<f32>,
|
||||
/// The color of the base of the track
|
||||
color_base: vec4<f32>,
|
||||
/// The color of the pulsing
|
||||
color_accent: vec4<f32>,
|
||||
};
|
||||
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(0)
|
||||
var<uniform> U: Uniforms;
|
||||
|
||||
// Distance to segment
|
||||
fn sdSegment(p: vec2<f32>, a: vec2<f32>, b: vec2<f32>) -> f32 {
|
||||
let pa = p - a;
|
||||
let ba = b - a;
|
||||
let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
|
||||
return length(pa - ba * h);
|
||||
}
|
||||
|
||||
fn sdArc(
|
||||
p: vec2<f32>,
|
||||
c: vec2<f32>,
|
||||
r: f32,
|
||||
a0: f32,
|
||||
a1: f32
|
||||
) -> f32 {
|
||||
let d = p - c;
|
||||
var ang = atan2(d.y, d.x);
|
||||
|
||||
var s = a0;
|
||||
var e = a1;
|
||||
if (e < s) { e += 2.0 * PI; }
|
||||
if (ang < s) { ang += 2.0 * PI; }
|
||||
|
||||
let on_arc = (ang >= s) && (ang <= e);
|
||||
|
||||
let circle = abs(length(d) - r);
|
||||
|
||||
let p0 = c + r * vec2<f32>(cos(s), sin(s));
|
||||
let p1 = c + r * vec2<f32>(cos(e), sin(e));
|
||||
let ends = min(length(p - p0), length(p - p1));
|
||||
|
||||
return select(ends, circle, on_arc);
|
||||
}
|
||||
|
||||
// Compute SDF and normalized distance along path
|
||||
fn trackSDF(p: vec2<f32>) -> vec2<f32> {
|
||||
let top_offset = U.thickness + U.border;
|
||||
let bottom_offset = U.size.y - top_offset;
|
||||
let midline = U.size.y / 2.0;
|
||||
let drop = midline - top_offset;
|
||||
let radius = drop / 2.0;
|
||||
|
||||
// Construct points
|
||||
let p0 = vec2<f32>(U.size.x - U.len_start, top_offset);
|
||||
let p1 = vec2<f32>(U.size.x - radius, top_offset);
|
||||
let arc0_center = vec2<f32>(p1.x, drop / 2.0 + top_offset);
|
||||
let p2 = vec2<f32>(p1.x, midline);
|
||||
let p3 = vec2<f32>(radius, midline);
|
||||
let arc1_center = vec2<f32>(p3.x, drop / 2.0 + midline);
|
||||
let p4 = vec2<f32>(radius, bottom_offset);
|
||||
let p5 = vec2<f32>(U.len_end, bottom_offset);
|
||||
|
||||
// Total length
|
||||
let L0 = length(p1 - p0);
|
||||
let L1 = PI * length(p2 - p1);
|
||||
let L2 = length(p3 - p2);
|
||||
let L3 = PI * length(p4 - p3);
|
||||
let L4 = length(p5 - p4);
|
||||
let total = L0 + L1 + L2 + L3 + L4;
|
||||
|
||||
var best_d = 1e9;
|
||||
var best_t = 0.0;
|
||||
|
||||
// Helper macro-like inline pattern
|
||||
// segment i with accumulated offset
|
||||
var accum = 0.0;
|
||||
|
||||
// p0->p1
|
||||
{
|
||||
let d = sdSegment(p, p0, p1);
|
||||
let ba = p1 - p0;
|
||||
let h = clamp(dot(p - p0, ba) / dot(ba, ba), 0.0, 1.0);
|
||||
let t = (accum + h * L0) / total;
|
||||
if (d < best_d) { best_d = d; best_t = t; }
|
||||
accum = accum + L0;
|
||||
}
|
||||
|
||||
// ARC p1->p2
|
||||
{
|
||||
let d = sdArc(p, arc0_center, radius, PI * 3.0 / 2.0, PI / 2.0);
|
||||
let ba = p2 - p1;
|
||||
let h = clamp(dot(p - p1, ba) / dot(ba, ba), 0.0, 1.0);
|
||||
let t = (accum + h * L1) / total;
|
||||
if (d < best_d) { best_d = d; best_t = t; }
|
||||
accum = accum + L1;
|
||||
}
|
||||
|
||||
// p2->p3 (full width)
|
||||
{
|
||||
let d = sdSegment(p, p2, p3);
|
||||
let ba = p3 - p2;
|
||||
let h = clamp(dot(p - p2, ba) / dot(ba, ba), 0.0, 1.0);
|
||||
let t = (accum + h * L2) / total;
|
||||
if (d < best_d) { best_d = d; best_t = t; }
|
||||
accum = accum + L2;
|
||||
}
|
||||
|
||||
// ARC p3->p4
|
||||
{
|
||||
let d = sdArc(p, arc1_center, radius, PI / 2.0, PI * 3.0 / 2.0);
|
||||
let ba = p4 - p3;
|
||||
let h = clamp(dot(p - p3, ba) / dot(ba, ba), 0.0, 1.0);
|
||||
let t = (accum + h * L3) / total;
|
||||
if (d < best_d) { best_d = d; best_t = t; }
|
||||
accum = accum + L3;
|
||||
}
|
||||
|
||||
// p4->p5
|
||||
{
|
||||
let d = sdSegment(p, p4, p5);
|
||||
let ba = p5 - p4;
|
||||
let h = clamp(dot(p - p4, ba) / dot(ba, ba), 0.0, 1.0);
|
||||
let t = (accum + h * L4) / total;
|
||||
if (d < best_d) { best_d = d; best_t = t; }
|
||||
}
|
||||
|
||||
return vec2<f32>(best_d, best_t);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(@builtin(position) frag_coord: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
let p = frag_coord.xy;
|
||||
|
||||
let res = trackSDF(p);
|
||||
let d = res.x;
|
||||
let t = res.y;
|
||||
// bounces back and forth between 0.0 and 1.0
|
||||
let t_wrap = abs(((t + U.time) % 2.0) - 1.0);
|
||||
// bounces back and forth between 0.0 and U.pulse_period
|
||||
let t_wrap_pulse = abs(((t - U.pulse_speed * U.time) % (U.pulse_period * 2.0)) - U.pulse_period);
|
||||
|
||||
let inner = U.thickness;
|
||||
let outer = U.thickness + U.border;
|
||||
|
||||
let aa = 1.0;
|
||||
|
||||
let inner_a = smoothstep(inner + aa, inner - aa, d);
|
||||
let outer_a = smoothstep(outer + aa, outer - aa, d);
|
||||
let border_mask = clamp(outer_a - inner_a, 0.0, 1.0);
|
||||
|
||||
// let color_mix = smoothstep(-0.1, 0.0, -abs(t - t_wrap_pulse));
|
||||
let color_mix = smoothstep(-U.pulse_width, 0.0, -abs(t - t_wrap));
|
||||
let inner_color = mix(U.color_base, U.color_accent, color_mix);
|
||||
// let inner_color = mix(U.color_base, U.color_accent, d);
|
||||
|
||||
let color = inner_color * inner_a + U.border_color * border_mask;
|
||||
let alpha = max(inner_a, outer_a);
|
||||
|
||||
return vec4<f32>(color.rgb, alpha);
|
||||
// return vec4<f32>(d / 3000.0, t / 3000.0, 0.0, 1.0);
|
||||
// return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
struct Uniforms {
|
||||
resolution: vec2<f32>, // viewport size (pixels)
|
||||
origin: vec2<f32>, // start point (pixels)
|
||||
|
||||
len0: f32, // first horizontal segment (to the right)
|
||||
len2: f32, // last horizontal segment (to the right)
|
||||
drop: f32, // vertical drop between segments
|
||||
|
||||
thickness: f32, // inner half-width
|
||||
border: f32, // border thickness
|
||||
|
||||
border_color: vec4<f32>,
|
||||
color_start: vec4<f32>,
|
||||
color_end: vec4<f32>,
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> U: Uniforms;
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position : vec4<f32>,
|
||||
@location(0) uv : vec2<f32>,
|
||||
};
|
||||
|
||||
@fragment
|
||||
fn fragment(mesh: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let u = clamp(mesh.uv.x, 0.0, 1.0);
|
||||
return vec4<f32>(mesh.position.x / 1000.0, mesh.position.y / 1000.0, 0.0, 1.0);
|
||||
}
|
||||
Reference in New Issue
Block a user