Procedural Flowers

2022
Creative CodingWebGLthree.js

Procedural parametric flowers in WebGL. Raytracing and a set of geometry algorithms produce a grid of flowers generated at load time.

A procedural system for organic shapes.

Where it came from

In 2022 I found Wolfram's math flowers: János Karsai's virtual flowers, built in Mathematica. His Virtual Flowers with Crispate Petals demonstration and his Flowers in Mathematica notebook draw polar plots where a single sine term folds a circle into petals. I wanted to know how far the idea could go. Every flower, generated in the browser from equations and a seed, then ray-traced right there on your GPU. It shipped on fxhash in 2023 as Study of Parametric Flowers, a grid of flowers generated the moment you load them. fxhash is gone now; the raster.art page survives, and the piece runs right here; every load rolls a fresh hash and generates a new set of flowers; the same hash always rebuilds the same ones.

The minted grid: every flower generated from the hash, no two alike

This one is live. It's a Lilium martagon from the generator; the equations below build the geometry, the spots and the stamens. It's rendered here in WebGL and you can see it render.

Two generators

The first generator extends the Wolfram idea to 3D. One function of angle sweeps the whole corolla:

r(φ) = (pLen · |sin(pNum/2 · φ)|^pSharp + fD) · t

pNum sets the lobe count and pSharp decides how deep the cuts go: near zero the petals fuse into a morning glory, high values cut them apart into a daisy. A height profile A · e^(−b·rᶜ) · rᵃ then bends the flat disc into cups, bells and trumpets.

The flat branch: a grid of corolla outlines from one equation, petal count across, sharpness down

A 21-petal daisy from the flat branch, the same equation at pSharp 2.2

A morning glory from the flat branch; pSharp near zero fuses the five petals into one funnel

That surface has a hard limit: r is single-valued per angle, so petals can never overlap. A poppy is impossible. The second generator, the spherical branch, winds one continuous spiral toward the pole of a sphere, and the petals are a modulation on the winding, an asin(sin(petalCutPow·θ)) cut that narrows the surface as it spins. Overlap follows automatically, because the surface passes over itself on the way in. The dahlias, camellias, zinnias and roses all come from this branch.

Petal count is just arithmetic, petals = cols · petalCutPow / 360.

The dahlia runs 2400 columns at power 10, which makes 67 petals, every time.

The spherical branch: the spiral seen from above, and the petalCut waveform that divides it into 67 petals

A poppy generated on the spiral branch: overlapping petals, fBm crinkle, no two folds alike

The crinkle in those petals comes from value noise over three octaves, displacing along the axis only, so the folds vary and the petal outline stays put.

Dots or no dots

Lily spotting is rejection sampling: pick a random point on a random triangle (barycentric; dots snapped to mesh vertices line up in visible rows), then reject anything closer to a placed dot than 1.5× its own long axis. Each spot is an ellipsoid stretched along the petal's uv gradient, so the markings run down the blade the way real papillae do, and they shrink toward the tip.

Whether a flower is spotted at all belongs to its palette. A tiger lily without spots is a contradiction, so tiger orange spots at 100%; cream trumpets usually go clean, so cream spots at 45%. The dot color is chosen together with the ramp: a maroon spot on a yellow tepal and a maroon spot on a red one are two different maroons.

Dotted flowers from the minted piece: polka spots, blotches and speckles, colors set by the palette

Where the spots go: seeded rejection sampling with keep-out radii, elongated along the blade

Lilium martagon from above, 952 spots placed one rejection test at a time

The stamens

Stamens ruined more renders than anything else. A filament is a curve swept with a varying radius (a tube geometry holds one radius, so I swept my own). The problem: filament length and splay look like two independent parameters and turn out to be coupled. The curve's outward reach at the tip is (stamenRadius/20) · stamenScale, so lengthening the filaments also moved them sideways. The fix that worked was to stop sampling and solve: pick how far the anthers should project past the tepals, derive the scale from that, then derive the radius back from a target spread. The pistil is measured against the stamens, because what your eye reads is whether the stigma clears the anthers and by how much.

Underneath a martagon: six filaments from one point, plump bowed anthers, the pistil clearing them

One number decides every color

The hash draws a single start hue, and everything else derives from it. Each flower in the grid steps around the color wheel from that hue by a scheme the hash also picks: complementary (+180°), triadic (+120°), quadratic (+90°), or a slow analogous drift of a few degrees per flower. The background comes from the same number, the start hue rotated by one of those harmony angles. The page's CSS variables and the loading screen derive from it too, so the UI always matches the flowers.

One hue, rotated: magenta flowers on a background the same wheel position chose

One hard rule: no green. The picker re-rolls until every flower avoids the band between 70° and 190° on the wheel, because green petals look like foliage. Within a flower, saturation and lightness vary with the radius, throat to margin. The piece shipped with four named lightness schemes (lightCenter, lightEdge, darkEdge, darkCenter); when I diffed them during a later cleanup, all four were byte-identical code with exactly four numbers changed. They're one function now, and the four numbers are a table.

The controls

The minted piece looks like a still image, but it carries a full set of controls. It starts with the loading screen: a 2000×2000 canvas handed to a second thread as an OffscreenCanvas, because the main thread is too busy building geometry to animate anything. It spins, tinted to the palette the hash already chose, and reports real progress (Building flowers: 64%, Placing dots: 91%), then ends with "Press F to debug, K for keybinds."

Those keybinds are real. K lists them, F opens the debug readout, and Z opens a panel of options: high-res dots, high-res stamens, transparent background, depth of field, auto-save PNG, overrides for the spherical flowers. The camera is orbit controls plus WASD/QE flying, with a slider that sets the step size, and more sliders that rotate the environment light around the scene. A pool of bin-packing workers places the flowers on the canvas by solving no-fit polygons, so flowers pack closely without overlapping.

The renderer

The realtime view is ordinary WebGL. A checkbox enables the path tracer, labeled "Raytrace high resolution PNG (warning: processor intense)": a full GPU path tracer (three-gpu-pathtracer) with a physical camera model. Real depth of field focused on the flowers, environment lighting from a blurred env map, and progressive sampling, so the frame starts noisy and refines the longer it runs. People assume Blender; the frames come from rays traced in a browser tab.

A path-traced frame from the piece: physical depth of field, no compositing

Spire flowers through the path tracer's focus

A print-quality frame exceeds the browser's maximum canvas size, so the export renders the scene in tiles (shifting the camera's view offset square by square) and dekapng stitches them into a PNG past any canvas limit. A generative token in a browser tab produces files large enough to print.

The stack

LayerChoice
PageIt's an html page
ModulesImport maps resolving bare names to vendored, pinned files
3Dthree.js r164 on the page, r148 for the convex-hull helpers
Ray tracingthree-gpu-pathtracer, on acceleration structures from three-mesh-bvh
LightingAn HDR environment probe through RGBELoader
GeometryA worker pool sized to your CPU cores, with an edge-collapse simplifier before geometry crosses a thread
DotsTwo workers per unique flower: rejection sampling, then a CSG intersect into the petal surface. Optionally a texture map and no CSG.
LayoutA genetic algorithm over no-fit polygons, clipper.js for the polygon math, in a worker that spawns its own
Loading screenA 2000×2000 OffscreenCanvas on its own thread
RandomnessThe fxhash snippet seeding an sfc32 PRNG; every draw in the piece pulls from it, so a flower is reproducible from its hash alone
Exportdekapng, writing PNGs past the browser's canvas size limit
TypeAuthentic Sans, on CSS custom properties fed by the same hue as the flowers
The martagon up topA GLB out of the generator on three r128, with GLTFLoader and OrbitControls, fetched lazily

Everything is vendored and pinned, so the piece theoretically renders the same way years from now. The main thread only draws; geometry, dots and layout all run on workers.

Where it's going

The current build treats each petal as its own surface: each tepal is a parametric patch (u along the blade, v across it) with a width profile in two halves that meet at the widest point with zero slope. Six blades share one axis, so each is confined to a sector of 2π/N, and the constraint halfWidth ≤ r · tan(π/N) is a hard boundary. The concave claw at a tepal's base follows directly from that clamp.

One tepal as its own patch: the two width-profile families, and the sector a blade is allowed to occupy

The current target is Lilium: 98 species tuned against reference photos, each rolled from triangular distributions so no single flower draws extreme values for every parameter at once.

Lilium pardalinum, the leopard lily: orange fading to gold, dark spots concentrated near the throat

Nine lilies, three species, three seeds each: lancifolium, martagon, superbum

Every image on this page came out of the generator this week. Same equations, new flowers.

Ricky Yurewitch

Procedural Flowers