What is a flow field?

A flow field is a grid of directions. Every point on it says which way to go. Give it to a drawing and the randomness stops scattering and starts flowing.

What a flow field is made of

Take a patch of space and put a number at every point. Turn each number into an angle. You now have a field of arrows, and anything you drop on it has somewhere to go.

Two choices make the field what it is.

Where the numbers come from
Almost always a noise function, usually Perlin or simplex. Noise gives values that drift smoothly rather than jump about. Neighbouring arrows end up pointing nearly the same way, so the field reads as a current rather than a mess.
What you do with them
A separate decision. Most generative art releases particles onto the field and draws their trails. You can also read the numbers as brightness, as a height, or as a nudge on some other choice.

The field is just the numbers. The picture is what you make of them.

The rest of this page shows that idea working, using Flield, a seamless background generator that runs in the browser. Every panel below is drawn live by the same code the app uses. Where a name is Flield's own rather than the general term, it says so.

The same composition, with and without a field

Both panels below use the same seed, the same density and the same colors. The only difference is whether a field is steering the result.

No field

With a field

Left, every cell decides on its own and the result is evenly scattered. Right, a field biases those decisions, and the same randomness gathers into bands and thins into valleys.

Six ways to draw one field

In most generative art, a flow field is a grid of vectors. You sample noise at each point, turn the value into an angle, and release particles that steer by whatever angle they are standing on. The particles trace the field, and the drawing is the trails they leave.

That is one thing to do with a set of numbers. Flield does it, and five others.

The six names below are Flield's, not the field's. They are worth a look anyway, because each is a different answer to one question: you have the numbers, so what do you draw?

TextureWhat it makes of the field
StreaksReads the value as a nudge on how likely each cell is to fill. Combed bands.
NebulaFolds the value at zero. The curves the field crosses there turn into bright filaments with dark gaps around them.
MarbleMoves where each cell reads from, using a second and coarser field. Then it does what Streaks does.
ContoursCuts the value into levels and draws the lines between them. A contour map of the field.
StreamlinesTurns the value into an angle and traces particles along it. The classic construction.
WeaveStreamlines twice over, the second set a quarter turn across the first.

The two furthest apart are explained in full below. The rest are variations on one or the other.

Streamlines: particles traced along the field

This is the classic construction. The left panel below is the field itself. One arrow per patch of grid, each pointing where a particle standing there would go.

The right panel is what you get by dropping particles on that field and recording where they went. Turn the curl up and the arrows swing further off the flow direction. The strands bend with them, because both are reading the same numbers.

The vectors

Particles tracing them

0.45
5.0

At zero curl every arrow points the same way and the particles run in straight lines. The noise is what makes a field a field.

Two details do most of the work here.

The trails land in the same grid of cells as everything else in the tool. That is how they end up mirrored, kaleidoscoped, masked to a circle and exported as SVG.

Streaks: noise stretched into bands

Streaks never builds a vector at all. That is why the tool's default output looks like woven fabric rather than like hair. What gets rotated and stretched here is the sampling space itself, before any noise is read from it.

Sample isotropic noise at plain grid coordinates and you get blobs with no preferred direction. So do two things to the coordinates first:

  1. Rotate them toward a heading.
  2. Divide one axis by a stretch factor.

The same noise function now returns values that vary slowly along that heading and quickly across it. Blobs become streaks, pointing where you aimed them.

function sampleFlowField(noise2D, x, y, { scale, angleRad, stretch, octaves }) {
  const cos = Math.cos(angleRad);
  const sin = Math.sin(angleRad);
  const rx = x * cos + y * sin;
  const ry = -x * sin + y * cos;
  const nx = rx / stretch / scale;
  const ny = ry / scale;
  return fbm(noise2D, nx, ny, octaves); // -1..1
}

Four lines of arithmetic, and they are the whole trick. rx and ry are the point rotated into flow-aligned space. Dividing rx by stretch compresses the coordinate along the flow direction, so the noise takes longer to change as you travel that way. Everything after that is ordinary noise.

The field drawn beside what it produces

The field is invisible in the finished artwork, so here it is directly. The left panel draws the raw value returned by that function at every point, dark for low and light for high. The right panel is the composition those values produce. Drag the sliders and watch them move together.

The field

What it produces

35°
4.0×

Direction rotates the sampling space. Stretch decides how far the noise travels before it changes, so a low value gives clouds and a high one gives long combed streaks.

Why the field nudges instead of switching

One more decision matters more than it looks, and it is a single line.

The obvious way to use a field is as a switch. If the value clears a threshold the cell fills, and otherwise it does not. That works, and it produces hard-edged regions with a visible boundary between filled and empty. Every cell on one side of the threshold agrees with every other.

Instead, the field adjusts the probability that a cell fills, and the cell still rolls its own dice:

let p = density;
if (useField) {
  const n = sampleFlowField(noise2D, x, y, { scale, angleRad, stretch, octaves });
  p = Math.min(1, Math.max(0, density + field.strength * n));
}
grid[y][x] = rand() < p;

Now a cell in a dense region is merely likely to fill, and one in a sparse region is merely unlikely. Regions blend into each other through a gradient of probability rather than meeting at an edge. The texture stays grainy everywhere instead of going solid where the field is strong.

That is the difference between the two panels in the first demo. It is one comparison operator.

Octaves: layering the noise

A single noise sample is smooth to the point of being bland. Sum several of them at doubling frequencies and halving amplitudes and you get fine detail on top of the broad shape, without disturbing the shape. The convention is called fractal Brownian motion.

That is the octaves argument. Two or three is usually enough here, because the grid is coarse and detail finer than a cell is wasted work.

The noise underneath is Ken Perlin's gradient noise, using the fade curve from his 2002 improved reference implementation, seeded so the same string always produces the same field.

Try it

Every control on this page is in the app, on each layer's tab, under Flow field. Texture picks which of the six you are drawing. Field Strength, Flow Scale, Flow Direction, Flow Stretch and Turbulence steer all six.

Some of them change name as you switch, because the job changes.

On Streamlines and Weave
Field strength becomes Curl, which is how far the noise swings a strand off the flow direction. Flow stretch becomes Trail length. Smoothing passes becomes Strand weight.
On Contours
Field strength becomes Levels, because what it buys there is a count of lines.

Open Flield, free with no signup, and drag them. The guide covers the rest of the tool.

Read next

The implementation is one file, generator.js, with no dependencies and no build step. It knows nothing about the interface, so it runs anywhere a canvas does. That is how the demos on this page work: the same functions, at a smaller size.

Flield is free for noncommercial use under the PolyForm Noncommercial License.