@vshaders/ease
Easing curves and motion-shaping functions as pure WGSL modules. An easing curve remaps animation progress: feed it a t that advances linearly and get back a t that accelerates, overshoots, rings, or bounces. Every module is functions only, no bindings, no entry points, so vgpu's resolver prunes whatever you don't import.
npm install @vshaders/ease@vshaders/ease/easing
The standard easing set as popularized by Robert Penner: ten curve families, each in three variants. All 30 functions share the signature (t: f32) -> f32 and map progress to eased progress with f(0) = 0 and f(1) = 1. In accelerates from rest, Out decelerates into the target, InOut does both.
| In | Out | InOut | Curve |
|---|---|---|---|
easeInQuad | easeOutQuad | easeInOutQuad | Power curve t², the gentlest polynomial. |
easeInCubic | easeOutCubic | easeInOutCubic | Power curve t³. |
easeInQuart | easeOutQuart | easeInOutQuart | Power curve t⁴. |
easeInQuint | easeOutQuint | easeInOutQuint | Power curve t⁵, the sharpest polynomial of the set. |
easeInSine | easeOutSine | easeInOutSine | A quarter period of a cosine; the softest curve here. |
easeInExpo | easeOutExpo | easeInOutExpo | Exponential, exp2(10t - 10): doubles ten times across the range. |
easeInCirc | easeOutCirc | easeInOutCirc | A quarter arc of a circle; vertical tangent at its steep end. |
easeInBack | easeOutBack | easeInOutBack | A cubic that pulls backward before traveling; overshoots by about 10%. |
easeInElastic | easeOutElastic | easeInOutElastic | An exponentially decaying sine: rings past the target, up to about ±37%. |
easeInBounce | easeOutBounce | easeInOutBounce | Four parabolic arcs of decaying height, like a dropped ball. |
Guarantees: t is expected in [0, 1] and is not clamped, so out-of-range inputs extrapolate the curve; clamp first (clamp01 from @vgpu/wgsl-std/math) if your driver can leave the range. Outputs are not clamped either: back and elastic intentionally leave [0, 1]. Endpoints are exact: expo and elastic guard their exponential edge cases, so easeInExpo(0) == 0, easeOutExpo(1) == 1, easeInElastic(0) == 0 and easeOutElastic(1) == 1 (and the InOut variants at both ends) hold precisely rather than to within 2⁻¹⁰. No NaNs from the domain edges: circ keeps its square roots real for out-of-range t, and polynomial powers are written as products rather than pow(), which WGSL leaves undefined for negative bases. The back and elastic shape constants are named consts in the module source (backOvershoot = 1.70158, elasticFrequency = tau / 3, and their InOut counterparts), with comments deriving them.
import { easeOutElastic } from "@vshaders/ease/easing";
import { linearToSrgb3 } from "@vgpu/wgsl-std/color";
struct Uniforms { resolution: vec2f, time: f32 }
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@fragment fn main(@builtin(position) position: vec4f) -> @location(0) vec4f {
let uv = position.xy / uniforms.resolution - vec2f(0.5);
let pop = easeOutElastic(clamp(fract(uniforms.time * 0.5) * 2.0, 0.0, 1.0));
let radius = 0.35 * pop; // ring pops in, wobbles, settles
let ring = 1.0 - smoothstep(0.0, 0.02, abs(length(uv) - radius));
return vec4f(linearToSrgb3(vec3f(ring)), 1.0);
}@vshaders/ease/shape
Motion-shaping helpers that don't fit the fixed-endpoint easing mold:
| Signature | Description |
|---|---|
smootherstep(edge0: f32, edge1: f32, value: f32) -> f32 | Ken Perlin's quintic step 6t⁵ - 15t⁴ + 10t³: like smoothstep but with zero second derivative at both edges, so chained motion has no curvature kink. |
almostIdentity(value: f32, threshold: f32, floorValue: f32) -> f32 | The identity for value >= threshold; below the threshold, the unique cubic that starts flat at floorValue and joins the identity at threshold with matching value and slope. Keeps a length or radius from reaching zero without a visible seam. |
springResponse(t: f32, damping: f32, frequency: f32) -> f32 | The unit-step response of a second-order system: starts at 0 with zero velocity and settles to 1. t is elapsed time, not normalized; frequency is the undamped natural frequency in radians per unit of t; damping is the damping ratio: 0 oscillates forever, values below 1 overshoot and ring, 1 is the no-overshoot limit. |
Guarantees: smootherstep clamps value to the edge interval, reversed edges (edge0 > edge1) fall from 1 to 0, and a zero-width edge (edge0 == edge1) degrades to a hard step instead of dividing by zero. almostIdentity expects value >= 0 and floorValue <= threshold; a non-positive threshold returns the input unchanged. In springResponse, negative damping is clamped to 0, and damping >= 1 degrades to the critically-damped response (the exact ζ = 1 curve) rather than the slower overdamped form.
import { springResponse } from "@vshaders/ease/shape";
// Inside a fragment shader with the usual Uniforms { resolution, time }:
// a bar that springs to a new width every second. Overshoots, rings, settles.
let sinceHop = fract(uniforms.time);
let goal = select(0.25, 0.75, fract(uniforms.time / 2.0) < 0.5);
let width = mix(1.0 - goal, goal, springResponse(sinceHop, 0.35, 18.0));
let bar = step(position.x / uniforms.resolution.x, width);Verify
npx vgpu check shaders/your-entry.wgsl --require-validationRuns on the entry shader that imports these modules: it resolves the import graph, validates the composed shader against a real device, and prints its reflection.
Provenance
The easing curve formulas are the standard set popularized by Robert Penner ("Motion, Tweening, and Easing", 2001; the equations are BSD-licensed and the closed forms are elementary math). smootherstep is Ken Perlin's quintic interpolant ("Improving Noise", SIGGRAPH 2002). almostIdentity is derived as the unique cubic satisfying four Hermite boundary constraints, a construction popularized by Inigo Quilez. springResponse is the textbook step response of an underdamped second-order system from control theory. All WGSL in this package is an original implementation, with edge-case guards (exponential endpoints, zero-width edges, non-positive thresholds, damping >= 1) in the argument ranges the math leaves undefined. MIT licensed.