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.

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.



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 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.



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.

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 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 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
| Layer | Choice |
|---|---|
| Page | It's an html page |
| Modules | Import maps resolving bare names to vendored, pinned files |
| 3D | three.js r164 on the page, r148 for the convex-hull helpers |
| Ray tracing | three-gpu-pathtracer, on acceleration structures from three-mesh-bvh |
| Lighting | An HDR environment probe through RGBELoader |
| Geometry | A worker pool sized to your CPU cores, with an edge-collapse simplifier before geometry crosses a thread |
| Dots | Two workers per unique flower: rejection sampling, then a CSG intersect into the petal surface. Optionally a texture map and no CSG. |
| Layout | A genetic algorithm over no-fit polygons, clipper.js for the polygon math, in a worker that spawns its own |
| Loading screen | A 2000×2000 OffscreenCanvas on its own thread |
| Randomness | The fxhash snippet seeding an sfc32 PRNG; every draw in the piece pulls from it, so a flower is reproducible from its hash alone |
| Export | dekapng, writing PNGs past the browser's canvas size limit |
| Type | Authentic Sans, on CSS custom properties fed by the same hue as the flowers |
| The martagon up top | A 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.

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.


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