Please note: this software is provided “as
is”. I built it for myself and decided to share it with others,
but I cannot give any warranties whatsoever - neither that it is fit
for any particular purpose, nor that it is free of faults. Use it at
your own risk.
Introduction
HeightSmith builds height, normal, ambient-occlusion and curvature
maps out of a stack of procedural generators. It runs on your own
GPU, in your own browser, and produces maps at up to 8192 ×
8192 in true 16-bit depth.
What the maps are called is not what they have to be used for. A
height map is a single channel of values, and a channel can drive
whatever a material asks for: the same output that displaces a
surface will serve as roughness, as specular or metallic, as a
blend mask between two materials, as opacity, or as the weight for
scattering foliage across terrain. Curvature makes a ready-made
edge-wear mask, and ambient occlusion a plausible dirt one. Build
the pattern you need and take the channel where you need it.
All of it happens on your machine. The page is loaded once and then
makes no network request at all - the browser is instructed to refuse
every one of them - so nothing is uploaded, nothing is measured, and
nothing you build here leaves your computer. There is no account, no
server and no upload button. There is no telemetry setting to find and
turn off, because there simply is nothing sent. We do not set any
cookie and we do not track you.
If you enjoy using HeightSmith, please consider sending a small
donation to paypal@xanathon.com, or visit my Ko-fi
page at ko-fi.com/xanathon.
It would make me very happy, and it would most likely be spent on
coffee. 😊
Pressing F1 in the app opens a short usage
introduction. This page is the long form: the same subjects with the
reasoning left in, plus the reference.
What it needs to run
A browser with WebGL2 and floating-point render
targets, which in practice means Chrome or Edge 80 and up, Firefox
113, or Safari 16.4. PNG export additionally uses the browser’s own
compression. If something is missing, the app says which capability it
is rather than failing silently.
The graphics card does not need to be new
Every timing on this page was measured on a GeForce GTX
1650 - a modest card from 2019 - rendering the heaviest of the
built-in presets:
A card of that age is enough because the work is a handful of
full-screen passes rather than millions of draw calls: one pass per
layer, whatever the layer draws. A faster card shortens those
milliseconds; it does not unlock anything. On a slower one the preview
still keeps up, because preview resolution is set separately from
export resolution.
Memory is the real ceiling
An 8192² ambient-occlusion or curvature export allocates roughly a
gigabyte of render targets, since it needs the blur scratch buffers as
well; height alone needs about half. Buffers are allocated only when a
path is used, so an export you never ask for costs nothing. If a card
cannot manage the largest size, drop to 4096² - the maps are
procedural, so the same setup renders at any resolution.
The upper limit is the GPU’s own maximum texture size, commonly
16384. The app offers up to 8192, clamps to what the card reports, and
tells you when it has.
How a map is built
A height map here is a single greyscale field, black at height zero
and white at full height, assembled in four steps:
-
Base level fills the whole image with one value.
Everything else is laid over it.
-
The layer stack runs bottom to top. Each layer
computes a pattern of its own and combines it with what the layers
beneath it have already produced.
-
Output shaping works on the finished field:
levels, gamma, quantisation, blur, inversion.
-
Derived maps - normal, ambient occlusion,
curvature - are computed from that field when you ask for them.
Height is carried in floating point from beginning to end and is only
reduced to whole numbers when a file is written. That is why a
16-bit PNG out of this program has genuinely different values in it,
rather than 256 levels stretched over a wider range.
The layer stack
Each layer is one card in the sidebar and one pass over the whole
image. Order matters: the stack is applied from the bottom card
upwards, so the topmost card is applied last and
therefore sits on top of everything below it.
Drag a card by its ⠯ handle to reorder it,
click its name to expand it, and use its checkbox to switch it off
without losing its settings. Only the handle starts a drag, so every
slider inside the card stays usable.
The buttons on a card
Calling a layer something
Double click the name in a card header and it becomes a field: type,
press Enter. Escape puts the old name back, and clicking away keeps
what was typed, the same as Enter. A locked layer refuses to be
renamed along with everything else it refuses.
Clearing the field is not an empty name, it is the layer going back to
being called what it is. Which is also why a project file carries the
property only for the layers somebody actually renamed: a stack of a
dozen would otherwise hold a dozen fields saying nothing.
Coverage
A generator produces two things per pixel: a height, and a coverage -
whether it is drawing there at all. Coverage is what makes the stack
compose. Where a layer covers nothing it contributes nothing, and the
layers beneath it show through untouched.
This is worth knowing because it explains results that otherwise look
like a bug. A scatter layer set to multiply darkens where its
boxes are and leaves the rest alone; the same layer set to
subtract does not carve a hole in the background between its
boxes. Opacity scales coverage rather than height, so
a half-opacity layer blends halfway towards its own result everywhere
it draws.
The padlock in a card's title row freezes that layer.
Its controls go dead, it cannot be removed or dragged to a different
position, and the two randomise buttons pass it by. It also stops
following the global seed: a locked layer keeps the seed it had, so
pressing New seed rerolls everything except the layers you
have settled on. Unlocking hands it back to the global seed, which
means it will change at the next reroll like any other.
Blending
Blend decides how a layer's height combines with the
height already accumulated beneath it. In the table below
b is what is already there and s is what this layer
produces.
The result is clamped to the 0..1 range after every layer, so
add and subtract cannot run away, but they can
flatten against the ceiling or the floor. If a stack goes white in
patches, that is usually add saturating rather than a
generator misbehaving.
The level window
Every generator emits a clean 0..1 pattern. Level min
and Level max squeeze that whole range into a window
before it is blended, which is how a layer is made to contribute a
faint break in a finish rather than full relief.
The window lives in the shared layer wrapper rather than in each
generator, so it also works for the ones that have no level control of
their own - noise, flow, veins and the circuit family included.
Invert beside it flips the pattern before the window
is applied: peaks become recesses while the window keeps saying which
brightnesses the layer may reach. Coverage is not inverted - where the
layer drew nothing it still draws nothing, since filling the tile with
everything it was not drawing would be a different pattern
rather than an inverted one.
Setting Level min above Level max inverts as well, and did so before
the switch existed. The difference is that the switch leaves the
window free: with it, direction and range are two controls instead
of one.
Masks
A mask limits where a layer applies. It multiplies the
layer's coverage, so a masked-out area is not darkened or flattened -
the layer simply is not there.
-
height tests what the stack has already
accumulated. A window from 0.5 to 1 puts the layer only on the
plateaus; 0 to 0.5 puts it only in the recesses. This is the one
that makes a stack look built rather than piled: rivets on the
plates and not in the gaps, rust in the hollows and not on the
ridges.
-
noise tests a fractal field instead, for patchy,
uneven weathering. Noise scale sets how large the
patches are.
-
Mask 1, Mask 2 and so on read one
of the numbered masks - see below.
-
gradient ramps across the image. It has a shape of
its own - linear for a fade from one side, radial
for one spreading out from a point - and a
Measured from anchor: the centre, or any of the
four corners.
Range from and Range to are the
window being tested, Edge softness feathers its two
edges, and Invert mask swaps inside for outside.
A radial gradient is scaled to reach the corner
furthest from its anchor, so its two ramp controls always span the
image. A linear one is deliberately left unscaled,
because rescaling a straight ramp to the image cancels the anchor
out entirely - it would have no effect at all. Left as it is, the
anchor decides where zero sits, which is why the ramp controls
reach below zero for a linear gradient and only there.
Direction belongs to the linear shape and is
hidden for the radial one.
Masks are per layer, so the same generator can appear twice in a stack
with two different masks and read as two different materials.
The numbered masks
The four built-in mask modes are set per layer, which means five layers
that want the same distribution carry five copies of it - and those
copies drift apart the moment one is adjusted. The Masks
block below the layer stack solves that: a numbered mask is set up once,
and any number of layers point at it.
A mask is any of the generators, with its own parameters and its own seed
offset. It draws nothing itself - it only produces a field for layers to
limit themselves with, and it appears in every layer’s
Mask by dropdown as Mask 1, Mask 2 and
so on.
Range from, Range to,
Edge softness and Invert mask still
belong to the layer, not to the mask. That is deliberate: two layers can
read one mask at different thresholds, one landing where it is bright and
the other where it is dark, which is most of the point of sharing it.
Each mask carries a Preview switch, which shows that
mask on the canvas instead of the layer stack - a mask you cannot see
is a mask you are setting up blind. One at a time, and it changes the
preview only: an export always renders the real map, whether a
preview is running or not.
Masks are not a second layer stack. They do not blend, do not stack and
cannot reference one another, so there is no order to get wrong and no
way to build a loop. The number is a name, nothing more - removing a
mask renumbers the rest and carries every reference with it.
Only a mask some layer actually reads is rendered, so one left defined
and unused costs nothing.
Placing a single object
Most generators here fill the frame with a lattice. The
Object layer does the opposite: it takes one module
out of that lattice and puts it where you want it. A hatch on its own,
a bore in a corner, an intake set at an angle - the same shapes the
grid layers are built from, at a position and size you choose.
The library holds every module that can stand alone, grouped by the
layer it comes from: twelve from Tech Tiles, twelve round fittings
under Discs & Hatches, the intake from Vent Louvres, the wheel
from Gear Rings, a screw, and six escape pods. For the ones lifted out
of a lattice there is one copy of the shape and both callers use it,
so a placed bore and a bore in the grid are the same code - they
cannot drift apart.
The Objects button beside Add layer opens the
library. The pictures in it are rendered by the generator itself rather
than drawn by hand - a picture cannot show something the shader does
not - but they are drawn ahead of time and ship with the app, so the
panel has them the moment it opens. A click drops that object in the
middle of the canvas as a layer of its own.
What you can set
Position X and Position Y place it,
Scale X and Scale Y size it - chained
while their values match, so dragging either one scales evenly until
the chain is opened - and Rotation turns it to any
angle. The two scale axes belong to the object rather than to the
image: rotate a wide object and its width turns with it.
Below those sit the controls the chosen object brings with it, and
only those: detail depth and a count for the round fittings, the full
set of louvre controls for the intake, the teeth and spokes for the
wheel. Where a shape has nothing to count - the cap once had a socket
fixed at six sides - the control is not shown rather than shown doing
nothing.
The screw
One entry in the library covers the lot, because a screw is two
choices rather than one shape. Head gives round,
countersunk, cheese or hex; Drive gives slot, cross,
a ribbed cross, a hex socket, a six-lobe recess, or none at all. Any
drive sits in any head, which is how it works on the shelf. The names
here describe the shapes rather than naming the brands that own the
familiar words for them.
The slot runs from rim to rim. Nothing sets its length - the head's
own outline cuts it off, so it ends exactly at the edge whatever the
head shape is, and only its width is yours to set. The other drives
have a size as well as a width, and the ones made of bars have both.
For the edge of the head there are three separate things, and they can
be combined: Head chamfer on the cheese head slopes
the face down to the rim, Bevel on the hex head rolls
the top edge over with a quarter round while leaving the face flat,
and Rim step cuts a shoulder into the edge at a depth
you set. A dome and a cone run out to the rim by themselves and have
neither.
Escape pods
Six entries, and between them they cover a good deal more than six
shapes, because what separates one from another is usually a number.
Shield is the blunt pentagon, and Fittings
decides what sits on it: nothing, rails across the field, shelves
following the outline, two leaves with a parting seam, a heavy boss,
or a ring of bolts. Pyramid hatch runs four facets to
a flat in the middle, with the size of that flat and its
Centre height yours to set - drop the middle below
the foot and the pyramid becomes a dish.
Plug sits in a socket with latch dogs on its corners
and takes its side count from the same control the round fittings
count with. Pod body is the pod itself, as a capsule,
a dome or a cone, and Hex pod is the six sided one.
Blast iris is a shutter, shut: flat blades all at one
height, each rising across its own width to the edge that lies over
its neighbour. Its hub can be switched off, and then the blades run
all the way to the middle.
Corner cut runs through the family. Nothing in this
group has a square corner, and the control says how far each one is
taken off; where a shape has no corner to cut - a capsule, a dome -
the control is not offered.
More than one of it
Array turns the single object into a row or a ring of
them. Along chooses X, Y or Ring;
Count says how many; Array rotation
turns the layout rather than the objects, so a row can run off at an
angle and a ring can start wherever you like.
A row only closes over the tile if its whole length is the width of
the tile, so Close over the tile takes the distance
out of your hands and sets it to one over the count. That is the same
rule every lattice layer here follows: seamlessness comes out of whole
numbers, not out of a repair afterwards. Switch it off and the
distance is yours, with the seam wherever it falls - measured on a row
of four at a free distance, the step across the seam was half a level
out of 255, against nothing at all with the box ticked. The array
rotation is offered only where it can work, which is on a free row or
on a ring.
A ring places the copies on a circle of a radius you set.
Turn with the ring has each one face outward, the way
bolts sit on a bolt circle; off, they all keep the angle of the
original. Where two copies overlap the taller one wins, so an array
reads as one piece rather than as heights adding up.
Random rotation gives every copy an angle of its own,
on top of whatever angle it already had - a ring that faces outward
still does, only scattered. Rotation seed rerolls
those angles and nothing else.
It still tiles
The object is placed in wrapped coordinates, so whatever leaves one
edge comes back in at the other and a project stays seamless wherever
the object is put. Measured on a bore pushed against the left edge:
the same amount of material across the middle of the frame as when it
is centred, split between the two edges, with no step at the seam.
A free angle is safe for the same reason it is not offered on the
lattice layers: one object does not repeat, so there is no seam for an
angle to break.
What the controls are called
The same words mean the same thing in every generator, which is what
lets you move between them without relearning anything.
-
Columns and Rows - how many cells
across and down. Always two axes, never one. A link toggle chains
them while their values are equal, so the square case stays a single
drag; unequal values simply stretch the cell.
-
Size - how large the thing inside a cell is,
relative to the cell.
-
Density - what fraction of the available places are
occupied. Only that. There is no separate coverage, fill or dropout
control anywhere in the program.
-
Scale X and Scale Y - the axes of
generators built on noise, which have no cells to count.
-
Height min and Height max - the
range a generator draws its own per-cell heights from, before the
layer's level window is applied to the result.
Because the axes are always separate, a round module on unequal axes
becomes an ellipse. That is intended: a squashed grid of ellipses is
usually what you want when you stretch a pattern, and forcing circles
would leave gaps instead.
Seeds and randomising
One number decides every random choice in the program. Set the same
Seed again and you get pixel-for-pixel the same maps.
-
Seed (Global) drives the whole stack.
New seed rolls it; the R key does
the same.
-
Seed offset on a card shifts that one layer to a
different variation without touching any other. Two copies of the
same generator with different offsets will not line up.
-
Variation seed inside a generator re-rolls only its
variation - which cells come out bright, which module lands where -
and leaves positions and sizes alone. It is a typed number with a
die beside it rather than a slider, because no seed is worth more
than its neighbour and dragging through them has no direction.
Randomise all in the Global block runs the gentle
variant on every layer at once. A few parameters are deliberately left
out of both randomisers: the ones that place a feature rather than
shape it, such as the centre of a radial pattern, where a random value
does not give you a different look but a broken one.
Output shaping
These act on the finished field, after every layer. They are the last
chance to fit the result to what your renderer expects.
Seamless tiling
Seamless tiling in the Global block is on by default.
It is a property of the generators rather than a filter applied
afterwards: there is no mirroring, no blending across the border, and
no soft edge. The seam is exact because nothing is ever drawn across
it in the first place.
Every noise and cellular function takes a period and wraps its integer
cell coordinates with it, so a cell at the right edge is literally the
same cell as the one at the left edge. Scatter layers are jittered
within a grid rather than placed freely, for the same reason.
Why some controls are counts and not angles
Anything slanted can only tile if its direction can be expressed as a
whole number of periods on both axes at once. An angle slider
almost never satisfies that, so where you might expect one you will
find two counts instead - a frequency across and a frequency down -
which span the direction between them and are periodic by
construction. Hazard Stripes and Knurling both work this way.
The three that cannot tile
Radial Rings and Frame cannot tile,
and neither can Aztec Hull Panels once it is switched
to polar. A radial field has no periodic edge, and a frame is an edge
by definition. Each says so in its own description, and each is still
useful with tiling on - as a mask, or on a surface that is not tiled.
To check a seam, use 2×2 tile check in the top
bar, or the 3×3 surface in the 3D view to see it
in relief, where a seam is much easier to spot than in a flat grey
image.
Global scale
Global scale in the Global block drives every layer's
repeat counts at once, so a stack that was built coarse can be made
fine without opening a single card. At 1.00 the stack
is exactly as you built it; below that, every pattern grows finer
together and keeps its proportions to the others.
The slider steps rather than glides, and that is deliberate. Tiling
here is built from whole numbers of cells per axis, so a scaled count
has to land back on a whole number or the seam stops closing. A layer
at 8 columns therefore changes on a different tick than one at 40, and
you will see parts of the stack move before others. The alternative -
a slider that glides and breaks the seam at almost every position -
was not worth it.
It only ever shrinks. A control that also went above 1 would have to
deepen the relief as well to keep a feature's shape, and the height
axis is a fixed 0 to 1 range with nowhere to grow: the peaks would
flatten against the ceiling while the pattern kept getting coarser. To
go the other way, lower the counts on the layers themselves.
Scale height too takes each layer's relief down with
its pattern, so a shape keeps its proportions instead of staying as
deep as it was. It is off by default, because a finer pattern at the
same depth is the more common intent and because a control called
Scale flattening your whole map is a surprise. It does nothing at
1.00.
The numbered masks come along. A mask that held its own scale while
the layers under it grew finer would slide out of proportion with the
pattern it shapes.
A few controls are widths in absolute units rather than fractions of a
cell - Greeble Panels gives its Gap and Bevel that
way - and those come down with the slider as well. Left alone they
would keep their width while the panels around them shrank, until the
seams were wider than the plates between them.
Below roughly a quarter scale such a seam is thinner than a pixel and
fades out rather than being smeared across one. That is the honest
result: a height map cannot hold detail finer than its own grid, and a
line drawn thinner than a pixel would alias rather than read as a
line. Raise the preview or export resolution and it comes back.
The object layer scales the other way round
This is the one place where the control does the opposite of what the
name on the card suggests, so it is worth knowing. Everywhere else,
Scale X and Scale Y are counts - how
many times the pattern repeats across the tile - and a smaller global
scale raises them, because more repeats means finer detail.
On the Object layer those same two labels mean the
size of the one object in the tile, not a number of repeats. There the
global scale multiplies them: at 0.50 an object set to 0.40
is drawn at 0.20, and it shrinks along with everything else rather
than growing to fill the space the finer patterns left behind.
An array's Count is left alone on purpose. Making the
pattern finer is not the same as asking for more copies, and the
number of copies in a row is something you set for its own sake.
Two layers have nothing to scale at all: Fill &
Gradient is a single ramp and Frame is a
single border, and neither repeats. They sit still while the slider
moves, which is correct.
The polar projection
Polar, in the Projection block at the top of a layer
card, wraps that layer round a centre: rings and sectors instead of a
square grid. A hull-plating pattern becomes a saucer, slats become
spokes, knurling becomes the top of a knob. It changes only the
coordinates the generator is read in - everything below it on the
card still means what it says.
Thirteen layers offer it: Greeble Panels,
Window Lights, Circuit Traces,
Pipe Runs, Truchet Conduits,
Box Scatter, Vent Louvres,
City, Bars & Slats,
Knurling, Hazard Stripes,
Hex Grid and Tech Tiles. The rest do
not, and the switch is simply absent on their cards rather than
present and disabled. A round module wrapped round a centre becomes a
crescent, and a continuous noise field becomes a smear; neither is
worth offering. Aztec Hull Panels has its own polar
mode, built into the layer, and is not part of this.
Placing the disc
Centre X and Centre Y put the middle
of the disc anywhere on the image. Disc radius sets
its outer edge; half the image is the largest a centred disc can be
without running past the edges, which is where the slider stops.
Moving the centre off the middle can still push it past one edge.
Hub radius leaves the middle blank, and its default is
deliberately not zero. Towards the centre of a disc the sectors
converge until a cell is narrower than a pixel, and there the edge
filtering has nothing left to work with: the middle would come out as
a patch of flat grey rather than as a pattern. A real saucer has a
blank middle for much the same reason. If you want the pattern to run
right in, raise the resolution rather than lowering the hub to zero.
What decides how many cells you get
This is the part that is not obvious from the controls, because
four things combine to produce the number of cells
around a ring, and two of them are on different parts of the card.
Worth reading once.
A ring is much longer round than it is deep - at the default radius and
hub, about eight times at the rim - so simply wrapping a square grid
round a centre would give wedges at the rim and needles at the middle.
The cell count is therefore not taken from Columns directly. It is
built up like this:
-
Rows, in the layer's own parameters, is the number
of rings the disc is divided into. It also sets the bands
the next three steps are worked out on: within one ring the count
around is constant, so cells never shear.
-
For each ring, how much longer it is than it is deep is measured -
call it the shape factor. It is small near the hub and large at the
rim: at the default radius and hub it runs from about 2.4 to about
8.4.
-
Ring density multiplies that shape factor.
-
The result is rounded to a whole number - it has to
be, or the ring cannot close on itself - and that whole number is how
many times Columns goes round this ring.
So the cells around one ring are Columns times a whole
number, and the two controls do different jobs.
Columns sets the overall fineness and multiplies every
ring equally; Ring density decides how the count is
distributed from hub to rim. At 16 columns there may well be 130
sectors at the rim, which is why Columns no longer reads as a literal
number of cells across once the projection is on.
A consequence worth knowing before it puzzles you.
Near the hub the shape factor is small - about 2.4 at the default - so
the whole number in step 4 can only be 1, 2, 3, 4 or 5. The inner ring
density control therefore has about five reachable settings there, and
neighbouring slider positions can give exactly the same picture. This
is not a rounding you can turn off; a fractional count would leave the
ring unable to close.
Raising Columns does not help with this: it
multiplies every count equally and leaves that whole number exactly as
it was. What helps is a larger hub, because it makes
the inner ring longer while making the bands shallower - the shape
factor rises and the steps get finer. At a hub of 0.12 there are five
settings at the innermost ring; at 0.20 there are nine, and at 0.30
twenty-one. If the inner control feels blunt, that is the dial to
reach for.
Ring density
The three Ring density controls are the multiplier
from step 3 above. They are three zones with hard borders:
inner owns the innermost third of the radius, middle the middle third,
outer the outer third, and none of them reaches into another's. Moving
one changes only its own part of the disc.
At 1, 1, 1, the default, every cell keeps the
proportions it has flat: square cells all the way from hub to rim. The
price is that the number of cells around has to change as the rings get
longer, and where it changes you may see a ring in the pattern. On a
broken-up pattern like Greeble Panels those are
invisible; on a strict lattice like Hex Grid they are
not, which is what the next control is for.
Pulling the inner value down thins out the middle of the disc the way
the paint on a real saucer section thins towards its hub - the usual
reason to touch these at all.
Even ring count
Even ring count takes one count for the whole disc,
measured at its middle, instead of one that follows the circumference.
Nothing then varies with the radius, so there is no place at which the
count could change and none of those rings can appear. The cells grow
outwards instead of staying square, which reads as a domed or curved
plate rather than a flat one.
This is usually what a strict lattice wants. On Hex Grid
it is the difference between a disc with two visible circular breaks in
it and one where the lattice runs unbroken from hub to rim.
With it on, the three zone controls collapse into a single
Ring density - there are no longer three zones to tell
apart - and that one slider sets the count for the whole disc.
Rows deserves a second mention here. It is the number
of rings, and it is also the grid the density profile is sampled on: a
disc with four rings can only change its count in four places, however
the three controls are set. If a profile you have dialled in seems to
be ignored, look at Rows first.
Four of the thirteen have no ring density at all -
Bars & Slats, Knurling and
Hazard Stripes are counted once across the whole tile
rather than in rows, so there are no rings for a count to vary between.
They get the plain projection, and the hub is what keeps their middle
usable. On Bars this costs nothing: its Angle already
decides between spokes and rings, and rings have no crowded middle.
Equal window size
Offered by Window Lights only, and only with the
projection on. Everything a generator draws sits inside a cell and
fills a fixed share of it, so on a disc it inherits whatever shape that
cell has: pinched near the hub, stretched at the rim. For a
subdivision - Greeble, Truchet, Pipe Runs - that is correct, because a
panel is its cell. For a window it is not. A window on a hull
is the same window wherever it sits, and here it is the flat projection
that is consistent and the disc that needs correcting.
With the switch on, every window is drawn at one size over the whole
disc. Measured at four radii on an even-count disc, the width of a
window ran 12.2, 16.0, 22.5 and 28.3 pixels without it, and 18.3, 18.4,
18.4 and 18.5 with it.
Rings too narrow to hold a whole window are left out
rather than given a squeezed one. That is not a shortcut: a squeezed
window touches its neighbours, and the ring turns into a solid band -
measured, 120 separate windows became three runs of a continuous line.
A real hull has no windows that close to its middle either, so leaving
them out is also the truthful picture. If you want them back, raise the
hub radius or lower the window size until they fit.
Snap to doublings
Snap to doublings rounds the number of cells around
each ring to a doubling, so that where the count changes, the coarse
ring's cell edges fall exactly on the fine ring's and the two line up
instead of clashing.
It starts off on twelve of the thirteen layers,
because it and the ring density controls pull against each other. With
the snap on, the count can only be a power of two, and most of each
density slider's travel then lands in the same one: the sliders stop
changing the number of cells around a ring and start moving the radius
at which the count jumps instead, which is not what they are for.
Box Scatter is the exception and starts with it on.
It reads its neighbouring cells to filter its edges, and without the
snap the disc leaves a visible join along one radius where it closes on
itself. Turn it on yourself anywhere the rings where the count changes
bother you more than the loss of range.
It matters with Even ring count as well, which is less
obvious: a count that never changes has no ring to line up, but what
the snap does for a layer that reads its neighbours is close the join
itself. Measured on Box Scatter, an even disc without the snap opened
that join to more than a hundred times its surroundings, and with it
the join reads as nothing at all.
Tiling, masks and the global scale
A disc cannot tile. It has no periodic edge, in the
same way Radial Rings and Frame do
not. The 2x2 tile check will show you four discs rather than one
continuous surface, and that is the honest answer rather than a fault.
What the projection does guarantee is that the pattern closes where it
meets itself going round the middle, with no seam along any radius.
Masks are unaffected by the switch. A mask says where on the
image a layer may contribute, and it stays in image
coordinates - so a gradient mask still runs left to right across the
picture rather than turning with the disc, which is what makes it
possible to aim one at part of the disc at all.
The global scale reaches polar layers correctly and
needs nothing extra: it drives the layer's own column and row counts as
it does everywhere else, and the rings follow from those.
The 3D preview
A grey image is a poor guide to what a renderer will do with it.
3D in the top bar shows the height field as geometry;
the same button, now reading Texture, brings the flat
map back.
- Plane - one tile, flat on.
-
3×3 - nine tiles, for judging a seam in
relief.
- Cylinder - hull sections, pipes, collars.
- Sphere - domes and saucers.
Drag to orbit, wheel to zoom, and Relief sets how far
the height pushes the surface out. Orbiting never re-runs a layer, so
it stays smooth however heavy the stack is.
It is a preview, not a render. The lighting is a single direct source
with no ambient occlusion in it, and your own software will differ -
particularly in how it interprets the height scale.
Expect it to look rougher than the real thing. The
preview displaces a fixed mesh of 512 × 512 quads -
768 × 768 for the 3×3 surface - and samples the
preview map rather than the export. Detail finer than that
grid falls between vertices, so crests come out faceted and edges
read as harsher and more ragged than they are.
That is the mesh, not the map. The same height field exported at
4096 or 8192 and applied to a dense mesh is smooth where the preview
looks blocky. Raising Preview resolution improves
what the surface samples, but the mesh stays the same size - so
judge shapes and seams here, and judge fine detail from the exported
map.
Export and file formats
Preview resolution and Resolution
under Export are separate on purpose: the preview stays responsive
while the export renders at full size, up to 8192 × 8192.
Everything is rendered and encoded in the page. Nothing is sent
anywhere to be converted, which is also why a very large export
briefly occupies the browser rather than a queue on a server.
All of them at once
Download all as ZIP writes one archive instead of one
file per button. Tick the maps you want — the list sits above the
button, with All and None beside it
— and every one of them is rendered at the export resolution and
packed together. Only the 16-bit height is ticked to begin with.
preset.json always goes in, whatever else is ticked. It is
the same file the Save .json button writes, so the
archive carries the recipe next to the result and the maps can be
rebuilt later at another size.
The Color entry is greyed out unless some enabled
layer actually produces colour, in the same way as the Color view on
the canvas. Nothing inside the archive is compressed a second time:
PNG and EXR arrive compressed already, so a second pass would cost
seconds at 8K and save almost nothing.
What the bar across the top means
A thin bar appears above the canvas whenever the page is busy with
something that would otherwise look like a freeze: a large export, a
change of preview resolution, and above all loading a project.
It runs on an estimate rather than on real progress, because the work
it covers holds the main thread from start to finish and nothing can
report from inside it. If the estimate falls short the bar slows down
near the end and waits; it only reaches the end when the work does.
When a project is loaded the bar starts as the file dialog closes,
which is often well before the page has the file: on Windows the
browser can sit on a freshly downloaded file for several seconds
before handing it over. That wait belongs to the browser and the
operating system, not to this page, but it is the wait you actually
sit through, so the bar covers it. It deliberately does not run while
the dialog is still open and waiting for you.
Colour
Everything else here is a single height field. Colour is a second pass
over the same stack rather than a change to that one: a generator may
describe what its parts are made of as well as how tall they are, and
only the generators that do take part. Motherboard is the first.
Color appears beside Height, Normal, AO and Curvature
in the top bar, and as an export. It is greyed out while nothing in the
stack has anything to say in colour, because a view that can only be
black is worse than no view at all.
In the 3D preview the same switch shades the surface
with that colour while the height goes on driving the relief - the board
is lit as geometry and coloured as a board, from one pass rather than
two renders that could disagree.
Colour follows a layer’s coverage and opacity, not its mask. A mask
decides where a layer contributes height, and reproducing that here
would need the height as it stood at that point in the stack, which
the colour pass does not have.
Reproducibility and sharing
Nothing here is baked into an image you cannot get back. Given the
same seed and the same settings, the program produces the same map at
any resolution.
-
Save .json writes the whole setup - seed, every
layer, every parameter, the output shaping - to a file.
Load .json reads it back.
-
Copy share link packs the same thing into the URL
fragment, the part after the
#, which browsers never
send to a server. A link therefore travels without the setup ever
passing through one.
-
Undo and Redo cover the whole
state, not just the last slider: Ctrl+Z and Ctrl+Shift+Z.
Files and links saved by older versions are meant to keep working.
When a control is renamed or split in two, the old key is mapped onto
the new one on load, so a link from before a change still opens the
setup it described. That said: it is a promise I try hard to keep
rather than one I can guarantee. If something does break, it will have
been an accident, not a decision.
Layer reference
Every generator, grouped as the picker groups them, with all of its
controls. The ranges and defaults below are read straight out of the
program when this page is built, so they cannot drift away from what
the sliders actually do.
Range is what a control accepts - the two ends of a
slider, or the entries of a dropdown. Controls that only appear in
certain modes are marked as such.
Sci-Fi / Hard surface 18 layers
Greeble Panels
Recursive panel subdivision with gaps and bevels. The hull-plating workhorse.
The one to reach for first. A rectangle is split in two, each half is
split again, and so on to Split depth, which is why the
panels interlock the way plating does instead of sitting on a grid.
Early stop is what keeps that from turning into a
uniform tartan: it lets a panel drop out of the subdivision early and
stay large while its neighbours keep dividing.
Two of these on top of each other is the standard hull. Put a coarse pass
underneath, a finer one over it on multiply, and the wide seams
of the coarse layer leave the fine one showing through as bright
separators - no third layer needed for the second grid level.
Height steps quantises the panel heights into plateaus,
which reads as machined rather than moulded.
Aztec Hull Panels
Subtle starship paint panelling, as seen on a certain science-fiction show. Polar for saucer sections, cartesian for the rest of the hull - same settings, so the two match.
The faint two-tone panelling on a starship hull, where the panels are a
paint scheme rather than relief. It is built for that specific job and
has the controls to match, so the two settings that matter most are the
ones you might otherwise leave alone: keep Contrast
low - a finished hull is barely there - and use
Grouping, which blends each panel toward a shared block
tone. Without grouping the result is noise in rectangles; with it, the
panels read as a design.
Polar (saucer) switches the same generator between
concentric rings and rectangular bands, deliberately from the same
settings, so a saucer and the hull sections around it match. Polar
cannot tile - a radial field has no periodic edge.
Repeats around is the motif count; about a dozen is what
the reference workflow uses, and that repetition is a large part of why
real aztecing looks designed.
This one is not a displacement map. Aztecing is a
difference in finish, not in depth - the panels are the same height and
catch the light differently, the way the panels of an aircraft or a
ship's hull do. Use the map on roughness or
specular, where it belongs, and at most faintly on colour.
So: this layer on its own, black background, exported as 8-bit. Keep
Contrast low - on the real thing the pattern is barely
visible until the light rakes across it, and the usual mistake is to
make it obvious.
Zig-zag folds walks the panel columns sideways and back
as they climb, which is how the warp pylons of the filming miniature are
patterned rather than the hull. Counted in whole folds over the height,
so it stays seamless.
City
Blocks, streets and buildings from above. Street width falls with every split, so avenues and alleys come out of one subdivision.
A city from above, and it is built out of the same recursive subdivision
as Greeble - with three things that subdivision alone cannot do.
The first is street hierarchy. Every cut lays a road along the line it
cuts, and each level's road is narrower than the one above it by the
Street taper, so the first cuts read as avenues and the
last as alleys. That is one subdivision, not layers stacked at different
scales, which is why the network hangs together: the gaps of a recursive
split always meet.
The second is that a block is not a building. Each one is subdivided
again, by Building depth, into separate footprints with a
Setback from the street. A large footprint is not allowed
to stop early - Building early stop applies to small ones
only - or a single slab fills the block and the plan reads as panelling.
The third is Centre height: tall in the middle, low at
the edge. It still tiles, since the falloff is symmetric and both edges
therefore meet at the same height, but every copy carries its own centre.
Repeat the tile and you get a grid of downtowns.
Narrowest building is what keeps the plan out of
slivers: a cut that would leave a piece thinner than this is moved in
until it would not, and an area too narrow to hold two is not cut at all.
Open blocks leaves some ground unbuilt, weighted towards
blocks that are both small and compact - a long thin one left open reads
as a trench through the plan rather than as a square. A footprint big
enough and square enough may be built around a courtyard;
the rest get a parapet and a housing from Roof detail,
which is most of what separates a building from a box.
Circuit Traces
Connected PCB-style routing with pads and vias. Traces agree across cell borders.
Traces that actually connect: each cell agrees with its neighbours about
what crosses the border, so a run continues instead of stopping at a cell
edge. Density is the fraction of the lattice edges that
carry a trace, which is the control that decides whether the board reads
as busy or as sparse.
Pads are placed on junctions by Pad chance, and
Via hole punches through their centres. Two passes at
different Columns - a coarse one on max and a
finer one over it - gives the mixture of trunk routing and fine detail
that a real board has.
Truchet Conduits
Interlocking arcs and straights. Reads as pipework, coolant channels or data bus.
Tiles carrying an arc or a straight, oriented so that whatever leaves one
edge arrives at the next. The result is always a connected network, which
is what separates it from a scatter: it reads as pipework or a data bus
because it goes somewhere.
Straight mix blends between curved and straight runs -
all arcs looks organic, all straights looks like a bus.
Groove carves a channel down the middle of each run,
turning a round conduit into a cable tray.
Box Scatter
The classic JSPlacement rectangle storm - grid-jittered so it stays seamless.
The rectangle storm the predecessors were built around, with one
difference: boxes are jittered within grid places rather than dropped at
free positions, so the layer tiles. That is also why the count is
Columns and Rows rather than a number of
boxes.
A wide gap between Size min and Size max
is what makes it look scattered rather than regular, and
Stretch pushes boxes toward bars.
Overlap wins decides which of two overlapping boxes is
seen: tallest keeps the stack readable, random is
messier and more organic.
Bars & Slats
Directional slats with jittered widths and dropouts. Vents, radiators, heat sinks.
Slats with jittered widths and dropouts - vents, radiators, heat sinks,
or a fast way to break up a flat panel. Segmentation
chops the bars into runs of separate tiles, which turns a louvre into a
row of modules.
Angle snaps to 0 or 90 degrees while seamless tiling is
on. A slanted bar can only tile if its slope closes a whole number of
periods on both edges at once, which an angle slider almost never does.
With tiling off, the angle is free.
Hex Grid
Hexagonal cells with gaps and per-tile height. Reactor plating, shield arrays.
Hexagonal cells with a gap and a bevel, for reactor plating and shield
arrays. The two axes are counted separately, as everywhere else, which
means the cells are only regular hexagons at about
0.58 rows per column - 16 columns to 9 rows, say. Away
from that ratio they stretch, which is usually what you want for a
panel run rather than a honeycomb.
Window Lights
Lit window runs for emission maps - rows and columns of panels with gaps, as on a hull. Not meant to be displaced.
Lit windows for an emission map. It is the one generator here not meant
to be displaced: black background, this layer on its own, exported as
8-bit PNG, and Hard on/off edges switched on if your
pipeline thresholds the map.
What sells it is that lit cells form runs - rows broken by gaps,
decks stacked above one another - rather than independent random dots.
Clustering goes further and puts only some blocks of hull
in use at all, as panels, decks or towers; a hull reads as built when its
sections repeat and as noise when every one of them differs. Stray
windows stay out of the blocks that clustering switched off, since a few
lights in every dark block would undo the clustering entirely.
To line the windows up with relief, build the displacement in a second
pass with the same seed and the same Columns and
Rows.
Tech Tiles
Each cell gets one of fourteen machine modules - rings, slots, crosses, stepped blocks, grilles, bolted plates, hex bosses, dishes, fans, terminals, lattices, stud arrays, hatches and capsule vents - each switchable and set to its own height.
Each cell gets one of six machine modules - ring, slots, cross, stepped
block, grille or bolted plate - so a single layer fills a panel with
varied hardware instead of one repeated shape.
Module set narrows that to a single kind when the variety
is too much.
Base plate is the height of the plate a module sits on.
At 0 the modules float, which is what you want when the layer goes over
plating that should stay visible between them.
Vent Louvres
Framed intake modules with angled slats. Reads as cooling, exhaust or sensor apertures.
Framed intakes with angled slats inside - cooling, exhaust, sensor
apertures. Unlike Bars & Slats, each module is a self-contained unit
with its own frame, so it can be scattered across a hull rather than
running the width of it.
Black background is the control to know. Off leaves
everything outside a module untouched, so the layers below show through
and the vents sit on existing plating. On drives the surroundings to
black, which is what you want when the vents are the whole map.
Hazard Stripes
Industrial warning banding, straight or chevron. Integer frequencies keep diagonals seamless.
Industrial warning banding, straight or folded into chevrons.
Chevron folds at 0 leaves plain diagonal stripes; raise
it for arrows.
There is no angle control here, and that is deliberate. The direction
comes from Frequency X and Frequency Y
together - two whole numbers of periods, which close on both edges by
construction. That is what lets a diagonal stripe tile at all.
Pipe Runs
Connected conduit with a rounded profile and periodic collars. Heavier and rounder than circuit traces.
Conduit run along grid edges with a rounded profile, heavier and rounder
than Circuit Traces and meant to read as plumbing rather than routing.
Roundness takes it from a flat strap at 0 to a full
semicircular pipe at 1.
The collars are what make it look installed rather than drawn:
Collar position places them along the run and
Elbow boss thickens the corners where a run turns.
Knurling
Crossed machine grooves - the diamond grip on a tool handle, a knob, a hatch lever. Also does straight and square knurls.
The crossed grip cut into a tool handle, a knob or a hatch lever.
Pattern chooses whether both sets of grooves are cut
(diamond), only one (straight), or the two cross at a right angle
(square).
Teeth across and Slant are two counts
rather than a count and an angle: the first is how many grooves cross the
tile, the second how far one climbs over its height. Together they are
the helix angle, and equal values give the classic 45 degrees - expressed
this way so the pattern still tiles. Wear takes the
height off the teeth unevenly, the way a handled tool loses it; at 0 the
knurl is straight off the machine.
Motherboard
A populated circuit board: chips, slots, sockets, capacitors and heat sinks on areas of unequal size, with bare substrate between them.
A populated board, and the reason it exists is the reason Circuit Traces
is not one: an even lattice reads as even, because every cell is the same
size and carries the same amount. Here the layout comes from recursive
subdivision instead, so a few areas end up large and many end up small,
and each is given a role - chip, slot, socket, capacitors, heat sink, or
nothing at all.
Split depth and Early stop are what
produce that spread of sizes; without the early stop the board comes out
uniformly fine again. Density decides how much of it
stays bare substrate, which matters as much as the components: a board
covered edge to edge reads as a texture rather than as a board.
An area's shape decides what it can be - a long thin one becomes a slot,
a squat one a chip or a socket - so the roll only chooses between the
plausible. Module set forces one kind everywhere.
Underneath it all runs the fine routing, so no part of
the board is truly empty, and Mounting holes cut
through whatever sits over them.
Routing style changes how a run turns: right angles is
the schematic look, rounded the older etched one, diagonal the 45 degree
mitre a board is actually routed with. Traces is different in
kind rather than in degree - it allows no junctions at all. A cell keeps
at most two of its four edges, so a run passes through or ends on a via,
and two runs can never meet or cross. That is what a single copper layer
is. Routing lanes then sets how far a run holds its
direction before it has to bend, and so how long the runs get.
Every kind has a switch, a share and a height of its own, and
All on / off carries the lot. A share decides how often
a kind is picked against the others; a height moves that one kind
against the global Component height. Up to 1 the height
scales it directly, above 1 it walks the rest of the way to the top of
the range, which 4 reaches exactly - so the whole travel does something
whatever the global height happens to be. Routing scale
sizes the wiring grid on its own, without touching Columns and Rows.
Object
One module from the library, placed on its own. The same shapes the grid layers are built from - a hatch, a bore, an intake - but at a position and size you choose rather than on a lattice.
Every other generator here fills the frame with a lattice. This one takes
a single module out of that lattice and puts it where you want it: a hatch
on its own, a bore in a corner, an intake set at an angle.
The shapes are not copies. The modules were lifted out of Tech Tiles,
Discs & Hatches, Vent Louvres and Gear Rings into one library that both
the grid layers and this one call, so a placed bore and a bore in the grid
are the same code and cannot drift apart. Alongside them stand shapes that
belong to no lattice: a screw, and six escape pods. The pictures in the
Objects panel are rendered by that same library rather
than drawn by hand, which is why they always show what you are actually
about to place; they are drawn ahead of time and ship with the app, so the
panel has them the moment it opens.
Scale X and Scale Y are chained while
their values match, so they behave as one size control until the chain is
opened. They belong to the object rather than to the image: rotate a wide
object and its width turns with it. Only the controls the chosen object
can use are shown - a count for the fittings that count something, the
louvre set for the intake, teeth and spokes for the wheel.
Because they are a size and not a count, Global scale
treats them the other way round to everywhere else. On a lattice layer,
Scale X is a number of repeats and a smaller global scale raises it. Here
it is the fraction of the tile the object fills, so a smaller global scale
lowers it: at 0.50 an object set to 0.40 is drawn at 0.20, and it shrinks
with the rest of the stack instead of growing into the space the finer
patterns leave behind. An array's Count is not touched -
a finer pattern is not the same request as more copies.
The screw is one entry rather than a dozen, because a screw is two choices
and not one shape: any drive sits in any head. Its slot is cut off by the
head's own outline rather than given a length, which is why it reaches the
rim on a hex head just as it does on a round one.
It tiles. The object is placed in wrapped coordinates, so what leaves one
edge returns at the other and a seamless project stays seamless wherever
it is put. That is also why a free angle is safe here while the lattice
layers do not offer one: a single object does not repeat, so there is no
seam for the angle to break.
Discs & Hatches
Round modules on a grid: hatches, iris blades, domes, bore holes, machined rings, dishes. Switch off the ones you do not want.
Six round module types behind six switches - hatch, iris, dome, bore,
rings, dish. Leave several on for a mixed field of hardware, or switch
all but one off when you want a specific part.
With Columns and Rows at 1 it becomes a
single module, and Offset X and Offset Y
slide it where you want it - the way to place one hero hatch rather than
a field of them.
Gear Rings
Toothed rings with hubs and spokes. Turbines, bearing races, reactor collars. Set Columns and Rows to 1 for a single hero wheel.
Toothed rings with hubs and spokes: turbines, bearing races, reactor
collars. Rim height and Web height are
separate, so the rim can stand proud of a recessed web the way a real
wheel does.
Set Columns and Rows to 1 for a single
hero wheel filling the tile. Note that a wheel is round while the two axes
are independent, so unequal counts give ellipses rather than circles.
Organic 14 layers
Fractal Noise
Seven fractal characters over one machine - soft, ridged, billowed, gradient, multifractal, eroded and marbled. The base coat for rock, cloud, tissue and rust.
The base coat for almost everything organic - rock, cloud, tissue, rust -
and the layer most often found at the bottom of a stack or, on
add at low opacity, at the top of one as grain.
Character changes what the octaves are summed into, and
it changes which of the other controls apply: the erosion, band and
veining settings belong to particular characters and stay hidden while
they would do nothing.
Domain warp feeds the field into its own coordinates,
which is what turns noise into marbling, flow or fibre. Keep
Lacunarity at 2.0 while tiling: other values drift the
octave periods apart and soften the seam, and the slider says so.
Basic Landscape
Terrain from fractal noise, with ridges, a sea level and a choice of edges. Islands, continents, ground planes.
Terrain rather than noise: the same fractal machinery, but with a sea
level, ridged crests and a choice of edges. Ridges takes
it from rolling hills at 0 to a mountain range at 1, and
Peak sharpness above 1 lifts the high ground while
flattening the low - which is what separates a range from a rough field.
Edges decides what the tile is. Sinking them to zero
makes an island on flat ground; keeping them at height makes a piece of
continuous terrain. Both tile: four all-zero edges meet each other just
as exactly as four periodic ones.
Voronoi Plating
Irregular cells with beveled seams. Turn jitter and warp down for welded hull plating, up for shell, scale or dried earth.
Cells with bevelled seams, and one of the most adjustable layers here
because Irregularity covers so much ground. Turn it
down, with the warp off, and it is welded hull plating; turn both up and
the same layer is shell, scale or dried earth.
The seams are where the height drops, so on max it reads as
plates standing proud, and on multiply as panel lines scored
into whatever is beneath.
Distance changes the geometry rather than the layout.
The seed points stay exactly where they are; euclidean rounds the cells,
manhattan cuts them to diamonds and chebyshev to rectangles. It is the
same plating in a crystalline register instead of an organic one, and
it tiles in all three.
Veins & Cracks
The boundary network of a cellular partition. Leaf veins, dried mud, capillaries.
The boundary network of the same cellular partition that Voronoi Plating
fills - the lines rather than the plates. Leaf veins, dried mud,
capillaries, crazed glaze. It is usually a subtract layer:
cracks belong below the surface, not on it.
Domain warp is what stops it looking calculated; without
it, the network is visibly the boundary of a regular partition.
Sub-branching lays a finer network inside the coarse one,
which is what real crazing does.
Distance straightens the cell edges the same way it does
in Voronoi Plating, turning a crack network into a fracture pattern.
Drawn from changes what is measured in the first place:
cell borders is the network, distance to seed draws a disc around every
point, and second nearest the overlap where two of those meet.
Weave
Threads crossing over and under one another. Cloth, basketwork, carbon fibre, wire mesh.
Two sets of threads at right angles, each passing over and under the
other in turn. Which one is on top at a given crossing is the entire
pattern, and it is the only thing the Weave control
changes: plain alternates every thread, twill steps the crossings along
by one each row so the floats line up into a diagonal, and basket takes
them in pairs.
Sag does more for the look than Crossing
lift does. A weave reads as woven because the thread underneath
is pressed down where it passes below, not merely because the one on top
stands proud - with the sag at zero the same geometry reads as a grid of
rods laid on each other.
Thread width is a share of the spacing, so below 1 the
ground shows between the threads and the layer becomes a mesh rather than
a cloth. Thread round at 1 is a true semicircle in cross
section; lower flattens it to a tape, higher draws it to a ridge.
The thread counts are rounded to the repeat of the chosen weave while
tiling - two threads for plain, four for the others - because a count
that does not divide by the repeat meets itself out of step at the seam
and one row of crossings comes out wrong.
Erosion is the one control here that does not run on the
graphics card. Water carves where it flows to, and a fragment shader can
only write the pixel it stands on, so the finished terrain is handed to
the processor, rained on, and handed back. Expect a wait of a second or
so whenever it or anything above it moves; the result is cached, so a
control that has no bearing on the terrain does not set it running again.
What it does is send drops downhill in their thousands. Each picks up
material while it has the speed to carry it and drops it where it slows,
and what makes valleys is the paths they share. Erosion
reach sets how far a drop travels: short cuts gullies into the
slopes, long joins them into valleys that cross the map.
Erosion inertia decides how much of its heading a drop
keeps - low follows the steepest way down and branches finely, high cuts
the corners and leaves broader channels. Deposition
decides how readily it lets go of what it carries, which is what silts up
the flats.
The field is computed at 512 whatever the render size is, and interpolated
above that: the work is per cell and on the processor, so a 4096 field
would stall the page for minutes. Erosion shapes are large ones, which is
what makes that trade bearable. Drops leaving one edge come back in at the
other, so an eroded map still tiles.
Cut corners clips corners at 45 degrees, which is the
one shape the set was missing: everything else here is square or round,
and the angular look of hard-surface panelling comes from the diagonal.
It is not drawn on top - the rectangle is intersected with a diagonal
half-plane, so the gap, the bevel and the antialiasing follow the cut
edge exactly as they follow every other. Corners cut
decides how many of the four are clipped. At 1 every corner is, and the
result is a field of octagons that reads as honeycomb; around half is
what reads as machined plate.
Cut corners clips corners at 45 degrees, which is the
one shape the set was missing: everything else here is square or round,
and the angular look of hard-surface panelling comes from the diagonal.
It is not drawn on top - the rectangle is intersected with a diagonal
half-plane, so the gap, the bevel and the antialiasing follow the cut
edge exactly as they follow every other. Corners cut
decides how many of the four are clipped. At 1 every corner is, and the
result is a field of octagons that reads as honeycomb; around half is
what reads as machined plate.
Cut corners clips corners at 45 degrees, which is the
one shape the set was missing: everything else here is square or round,
and the angular look of hard-surface panelling comes from the diagonal.
It is not drawn on top - the rectangle is intersected with a diagonal
half-plane, so the gap, the bevel and the antialiasing follow the cut
edge exactly as they follow every other. Corners cut
decides how many of the four are clipped. At 1 every corner is, and the
result is a field of octagons that reads as honeycomb; around half is
what reads as machined plate.
Cell Bodies
Rounded blobs that fuse where they touch. Bubbles, ova, coral polyps, bone marrow.
Rounded bodies that melt together where they touch - bubbles, ova, coral
polyps, marrow. Fusion is the control that matters:
at 0 the blobs are separate pads, and as it rises they merge into a
continuous mass with the joins still visible.
Dome falloff shapes each body, from flat pads at low
values to sharp pustules at high ones.
Flow Fibres
Streaks advected along a curl-noise field. Muscle fibre, wood grain, brushed metal, smoke.
Streaks dragged along a swirling field - muscle fibre, wood grain,
brushed metal, smoke. By default the field turns freely, which gives
turbulence; Bias direction pulls it toward one angle and
turns that turbulence into grain.
Advection steps is how far each streak is followed.
Longer streaks cost more, and it is the one control here worth raising
last.
Radial Rings
Concentric rings and spokes around a point. Iris shutters, tree rings, shockwaves. Cannot tile seamlessly - a radial field has no periodic edge.
Rings and spokes about a point: iris shutters, tree rings, shockwaves,
the structural rings on a saucer. Wobble puts noise on
the radius, which is the difference between a machined ring and a growth
ring.
Cannot tile. A radial field has no periodic edge, and
no setting changes that. It is still useful with tiling on - as a mask,
as a hero element in the middle of a panel, or on a surface that is not
tiled - but the seam will show if the map is repeated.
Scales
Overlapping shingles that lap over one another. Fish, reptile, pinecone, armour, roof tiles.
Shingles that lap over one another: fish, reptile, pinecone, armour, roof
tiles. Row offset at 0.5 staggers alternate rows, which
is how scales normally sit; 0 stacks them in columns, which reads as
tiling rather than skin.
Taper narrows the trailing end into a teardrop instead of
an ellipse, and Edge lip raises the free edge so each
scale catches the light along the edge it overlaps with.
Craters
Impact bowls with raised rims and ejecta. Moons, asteroids, corroded metal, blistered paint.
Impact bowls with raised rims and ejecta - moons, asteroids, corroded
metal, blistered paint. Rim frequency sets how many
lobes run around the rim: low gives a lopsided bowl, high a scalloped,
eroded edge.
Shy (no overlap) makes craters shrink rather than cut
into each other. Rims stay intact and the sizes come out more varied,
which is usually the more convincing result; switch it off when you want
a surface that has been hit repeatedly in the same place.
Dunes & Ripples
Asymmetric wind ridges with a steep lee side, modulated by noise. Sand, snow drift, brushed grain.
Wind ridges with a steep lee side. Crest position is the
whole point of the layer: at 0.5 the profile is a sine wave, and moving
it off centre is what makes the ridge look formed by wind rather than by
mathematics.
Meander bends the ridges so they wander,
Amplitude variation lets them fade in and out instead of
running edge to edge, and Ripple frequency lays the fine
secondary ripples over the top.
Blotches
Irregular patches from thresholded warped noise. Camouflage, lichen, rust bloom, cow hide, moss.
Patches with irregular edges - camouflage, lichen, rust bloom, moss, cow
hide. As a height layer it is a raised or sunken patch; as the source for
a mask it is one of the more useful things here, because the patches have
the shape that weathering actually has.
Keep Warp reasonably high. Low values look like
thresholded noise, which is what they are; the warp is what gives the
edges their reach and turns them into growth.
Inner holes punches a second threshold out of the
patches, for lichen and flaking paint.
Wrinkles & Folds
Creased ridges from self-warped folded noise. Skin, brain, leather, crumpled cloth, magma crust.
Creased ridges from a field folded into itself - skin, brain, leather,
crumpled cloth, magma crust. Self warp is the source of
that folded look, and Crease sharpness decides whether
the creases are soft rolls or cut lines.
Stretch elongates the folds along one axis, which is
what turns skin into grain, muscle or bark.
Surface Wear
Scratches, dust, streaks and drips. What has happened to a surface since it was made, rather than what it is made of.
What has happened to a surface since it was made, rather than what it is
made of. Mark picks between scratches, dust, streaks and
drips, and the rest of the controls follow that choice.
Wander deserves a word. At 0 every mark is a ruled line
of constant width - which is what a machine leaves, and what a used
surface almost never shows. Raising it lets the mark curve away from its
path, and that alone is most of the difference between damage that reads
as real and damage that reads as drawn. Runs tells the
layer which way gravity points, so streaks and drips fall the right way.
Basic 8 layers
Fill & Gradient
A flat level, a linear ramp or a radial falloff. Base plates and global shaping.
A flat level, a linear ramp, a radial falloff or a box. Rarely
interesting on its own, and constantly useful: as a base plate under a
stack, as a global shape that makes a flat tile read as a curved panel,
or on multiply to fade a layer out toward the edges.
Ease bends the ramp, so the fade need not be linear.
Checker
Alternating squares. A base plate, a two-level mask, or a scale reference.
Alternating squares - a base plate, a two-level mask, or simply a scale
reference while you dial in the resolution of something else.
Second level is the height of the alternate squares, so
the contrast between the two is yours to set rather than fixed at black
and white.
Dot Grid
A regular lattice of discs, squares or diamonds. Rivets, perforations, halftone.
A regular lattice of discs, squares or diamonds: rivets, perforations,
halftone. Dome takes them from flat pads to rounded
studs, and Row stagger at 0.5 turns the square lattice
into a staggered one.
Offset X and Offset Y slide the whole
lattice in cells, which is how you line a row of rivets up with the
panel edge underneath it rather than hoping the two agree.
Tile Sampler
One shape stamped across a grid, each instance turned, sized and shifted on its own. The general case behind most scatter patterns.
The general case behind most of the scatter layers here: one shape
stamped across a grid, with every instance turned, sized and shifted on
its own. When no specialised layer fits what you want, this is usually
the one that does.
Rotation steps is the control worth understanding. At 4
the stamps snap to quarter turns, which reads as a panel layout; at 0 they
turn freely, which reads as debris. Aspect stretches the
stamp itself before it is turned, so a square becomes a bar.
Bricks
Offset rows of rectangles with mortar joints. Masonry, cladding, panel runs.
Offset rows with mortar joints - masonry, cladding, panel runs.
Row offset is the bond: 0.5 is a running bond, 0 is a
stack bond, and the values between are the patterns bricklayers argue
about.
Waves
Sine, triangle, saw or square waves. Corrugation, ripples, ridging, interference.
Sine, triangle, saw or square, in whatever combination of the two axes
you set. Corrugation, ripples, ridging, interference.
Cross wave mixes in the same wave rotated a quarter
turn, which is where the grid and interference patterns come from.
Warp puts noise on the phase and turns machined ridges
into flowing ones. The two frequencies are whole numbers because that is
what keeps a diagonal wave seamless.
Value Mosaic
One flat random height per grid cell. Pixel terracing, panel randomisation, base noise.
One flat random height per cell. Pixel terracing, panel randomisation, or
a base noise with no gradients in it at all.
Interpolate smooths between the cells instead, which
turns the same layer into a very coarse, very cheap noise.
Frame
A rectangular border or vignette measured from the edges. Cannot tile - it is an edge by definition. Best used as a mask over other layers.
A border or vignette measured in from the edges.
Border width is in fractions of the image rather than in
pixels, so a hairline stays a hairline at any export resolution.
Cannot tile - it is an edge by definition. It is at its
most useful as a mask over other layers, keeping them away from the
border or confining them to it.
Under the hood
For anyone who wants to know what this is made of. None of it is
needed to use the program.
What it is written in
TypeScript, compiled to ES2022 modules, in strict mode with the
stricter options switched on as well - unchecked index access, unused
locals and parameters, implicit overrides. Around 12,900 lines across
the application, the generators and the tooling. The generators
themselves are GLSL ES 3.00, carried in the TypeScript source as
strings and compiled by the browser at runtime.
There are no runtime dependencies. Not a framework,
not a UI library, not a maths library, not a shader helper: the
shipped application contains only code written for it. Two packages
are used to build it - TypeScript and Vite - and neither ends up in
what you run.
The interface
Plain DOM, built and updated by hand, with CSS custom properties for
the theme. Every control on the right is generated from the same
declaration the shader reads its value from, which is why a new
control appears in the panel, in a saved link and in the reference on
this page without being wired up three times.
The graphics
WebGL2. The layer stack is drawn into 16-bit floating-point render
targets, ping-ponging between two of them, which needs the
EXT_color_buffer_float extension - the one hard
requirement beyond WebGL2 itself. Export at 16 bits per channel is
possible because the buffers were always that precise; nothing is
widened after the fact. Where a half-float target is unavailable the
renderer falls back, and says so.
Building it
Vite for development and for the production build, with esbuild
underneath. It builds as two pages rather than one - the application
and this documentation - so the documentation is a sibling of the app
and can be opened, linked and deployed on its own. The reference
chapters are generated by a Node script that bundles the layer
registry and reads the controls straight out of it, so they cannot
drift away from the program.
The whole application is about 286 kB of JavaScript, 83 kB gzipped,
plus 18 kB of CSS. There is nothing to install and nothing to serve:
the built folder can even be opened from disk.
Where your work is kept
A setup lives in the address bar. The whole state - layers, controls,
masks, seed - is encoded into the URL fragment, which is why a link
restores exactly what you had and why it never reaches a server: a
fragment is not sent with a request. The only thing in local storage
is the width of the control panel.
What the page is allowed to do
The shipped page carries a content security policy that permits
nothing beyond itself: no outbound connections at all, no external
scripts, no forms, no framing. It is not a promise in prose but a rule
the browser enforces, and it is the reason the privacy note above can
be as short as it is.