The Parametric Singularity
A lightning bolt with no model file — a high-segment cylinder displaced along its own normals by 3D noise every frame, orbited by fourteen shards on an underdamped spring.
Heracles: Foundational labour. Difficult, necessary technique.
Why it matters
The instinct when a brief says "lightning" is to model a lightning bolt: open a DCC tool, extrude a jagged form, export a mesh, ship the mesh. That produces a static object that must then be animated around — rotated, bobbed, flickered with an opacity keyframe — and it always reads as a prop.
This bolt has no model file. It is a CylinderGeometry with 140 height segments, handed to a vertex shader that pushes every vertex along its own normal by the value of a 3D noise field sampled against uTime. The silhouette is therefore recomputed sixty times a second and never repeats. There is nothing to animate around, because the object is the animation.
The wider lesson is about where you spend authoring effort. A modelled bolt costs artist hours, a file download, and a fixed identity. A parametric one costs about forty lines of GLSL and gains a control surface: frequency, amplitude, and octave count are all uniforms, so the same object can be a hum or a catastrophe depending on how fast the reader is scrolling.
Key techniques
- Vertex displacement along
normal.xzonly, so the bolt frays sideways and keeps its length. Displacing along the full normal makes the tips breathe, which reads as a balloon rather than a discharge. - Noise frequency driven by input:
5.0 + abs(uScrollVelocity) * 3.0. The idle state is a slow crackle; a fast scroll shortens the wavelength and the object visibly tightens. - Two octaves, with the second gated behind a
uOctavesuniform the degradation layer drops to one. Halving the noise cost changes the fine crackle and not the silhouette, which is exactly the right thing to lose first. - Fresnel in the fragment shader as
pow(1.0 - dot(vNormal, viewDir), 5.0), mixed from#00d2fftoward blown-out white and multiplied above 1.0 so bloom has real energy to find. AdditiveBlendingwithdepthWrite: false, and a second thinner unlit core mesh inside the shader mesh — one pass cannot be both a hot filament and a soft corona.- Fourteen
MeshPhysicalMaterialshards atroughness: 0.02, laid out deterministically rather than randomly, so the composition is the same for every visitor. - Shard return on a critically-underdamped spring (ω 18, ζ 0.35) rather than a lerp, because struck metal overshoots once and then settles.
What to steal
The transferable part.Compute the object instead of shipping it. Any form whose identity is "irregular energy" — lightning, cracks, fire, fracture, erosion — is cheaper and better as a displaced primitive than as a mesh. You gain a uniform-level control surface for free.
Make the octave count a uniform. This is the single most useful performance lever in a noise-based shader. It lets a degradation system reduce cost by 40% with a change no one notices, instead of switching off the effect entirely.
Springs, not lerps, for anything with mass. ζ = 0.35 with ω = 18 gives one clean overshoot. A lerp of any factor is asymptotic and therefore weightless: it never arrives, and nothing that never arrives feels physical.
Lay out "chaos" deterministically. Randomising positions at mount means the composition is different for every visitor, which means there is no composition. Use index-derived trigonometry and keep art direction.
What not to copy
Context-specific or costly.Do not raise the segment count to chase smoothness. Vertex displacement cost is linear in vertices, and past roughly 140 height segments the difference is invisible at any sane camera distance while the cost is not.
Do not sample noise in the fragment shader for the silhouette. Silhouette belongs in the vertex stage, where the cost is per-vertex; the fragment stage should only be deciding colour and alpha, where it is per-pixel.
Do not stack additive passes to get brightness. Three additive layers is three times the fill rate for something a single luminanceThreshold on the bloom pass gives you for one.
Inclusion rationale
Admitted because it answers a question that generalises well beyond lightning: when is an asset the wrong answer? It is also the clearest demonstration in the canon of a uniform-level degradation path — one shader, one quality lever, one measurable saving.
Technical register
Performance
Budget: 16.6ms, held. On an M2 the whole hall — bolt, shards, 3,600-point floor field, bloom, vignette — runs at 60fps at DPR 1.75. The frame probe samples 90 frames; below 50fps it demotes once, permanently, to DPR 1.0, one noise octave, 60 height segments and a 34×34 floor. It never promotes back: oscillating between tiers is worse than sitting in the lower one.
The shard springs are integrated in JavaScript because fourteen springs is nothing, and moving them to a shader would cost the ability to read their positions back. Delta time is clamped to 1/30s so a stalled tab cannot resolve into an explosion on the next frame.
Mobile
Scroll velocity is fed from touchmove deltas at the same normalisation as wheel, so the charge behaviour exists on touch and is not a desktop-only reward. Antialiasing is off in the reduced tier and the vertical camera drift is reduced, because handheld drift on a handheld device is redundant.
Accessibility
prefers-reduced-motion zeroes the velocity term entirely: the noise still animates at a slow constant rate so the object is not dead, but nothing tracks input and nothing overshoots. Arrow and Page keys inject the same velocity impulse as the wheel, so the discharge is reachable without a pointer. The name, the weapon, the technical readout, and every navigation control are real DOM text above the canvas — the canvas carries no information that is not also written down.
Study next