Robert Monfera
twitter bl.ocks linkedin github stackoverflow email

WebGL marker rendering and tweening with the superformula

This is an article about using the superformula for rendering different marker shapes and their tweening.

Tooling

This isolated example uses

Update: WebGL2 has standard derivatives incorporated into the standard, so it's no longer an extension that may or may not be available. The example isn't updated since as of early 2022, REGL targets WebGL1 rather than WebGL2.

Superformula for marker rendering and tweening

Markers are typically created as HTML, SVG or Canvas2d shapes. Even in WebGL, shapes are usually represented via different shaders (one shader program for rectangles; one for triangles etc.), or different meshes (triangle tesselation).

This example experiments with a single-shader approach using the superformula that works with points (note: the WebGL standards don't guarantee points larger than 1px by 1px) or quads (two triangles forming a rectangle). So the shape is entirely controllable via GL uniforms.

Pros: most if not all dataviz marker shapes are doable; single-shader, single-geometry call, the shapes controllable via uniforms; tweening between shapes; easy to extend to per-point or per-batch marker shape (with attributes or instancing, respectively).

Cons: It's not performant, due to all the trigonometric functions in the fragment shader. Also, it's sensitive to numerical precision issues (needs precision highp float;); shape edges have some curvature.

Upshot: The example is just inspiration for thinking of shapes as math functions that can be represented and maybe smoothly and sensibly tweened in shaders, as opposed to a triangle mesh or fixed shapes. For high performance, batch use cases, it's still best to go with fixed, shape-specific shader programs, executed in bulk.

Position tweening with WebGL

In HTML, SVG, Canvas2d and undemanding WebGL cases, tweening is achieved via CSS transitions (HTML, SVG) or via modifying the positions in JavaScript 30-60 times a second via requestAnimationFrame. In case of WebGL, this would require the recalculation of element positions and a re-transfer of the position array attrib 30-60 times a second. It's sometimes good enough, but doing calculations in single-threaded JavaScript and crossing the CPU-GPU boundary in rAF loops with potentially unbounded position buffer sizes will hurt performance.

Instead, the idiomatic WebGL use involves the calculation and GPU transfer of both the start and end positions upfront. Then the animation needs no significant data transfer. The rAF loop will just update the shader uniform variable responsible for tweening, for example, between zero and one. The fragment shader computes the ever current interpolated position. It's also possible to do all kinds of easing curves instead of linear interpolation.

This example performs a simple easing with Ken Perin's smootherstep, an improvement over GLSL's built-in smoothstep.

Code

More code comments and reference links in this bl.ock.

See the Pen WebGL markers by Robert Monfera (@monfera) on CodePen.

DM me on twitter if it doesn't work properly on your specific platform.