struct Camera { scale: vec2f, centre: vec2f, }; struct MaterialDef { color: vec4f, // the length of the path segment for t length: f32, }; struct VfxLine { a: vec2f, b: vec2f, width: f32, material: u32, alive_for: f32, lifetime: f32, } @group(0) @binding(0) var camera: Camera; @group(0) @binding(1) var materials: array; @group(0) @binding(2) var vfx_lines: array; struct VertexOutput { @builtin(position) clip_position: vec4f, @location(0) @interpolate(flat) color: vec4f, // the length of the path segment for t @location(1) @interpolate(flat) length: f32, @location(2) @interpolate(flat) cur_centre: f32, @location(3) dist: f32, }; @vertex fn vs_main( @builtin(vertex_index) i: u32, @builtin(instance_index) n: u32, ) -> VertexOutput { let vfx_line = vfx_lines[n]; let d = vfx_line.b - vfx_line.a; // (-y, x) is a 90deg ccw rotation let p = normalize(vec2f(-d.y, d.x)) * (vfx_line.width * 0.5); // 0=a,1=b let cv = f32(i & 1u); // direction to offset the orthogonal width let co = f32(i >> 1u) * 2.0 - 1.0; // select a/b, add offset let world = mix(vfx_line.a, vfx_line.b, cv) + p * co; var out: VertexOutput; out.clip_position = vec4f((world - camera.centre) * camera.scale, 0.0, 1.0); // material let material = materials[vfx_line.material]; out.color = material.color; out.length = material.length; // computed let l = length(d); out.cur_centre = (l + material.length) * (vfx_line.alive_for / vfx_line.lifetime) - (material.length / 2); // interpolated out.dist = cv * l; return out; } @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f { // distance on line of the centre of the current (w.r.t. time) line segment // can do this in vertex // our distance along the line vs the current centre let dist_from_centre = abs(in.dist - in.cur_centre); let c = in.color; return vec4f(c.r, c.g, c.b, c.a * (1 - smoothstep(0.0, in.length / 2, dist_from_centre))); }