Skip to content

Source: noise.h

noise

A smooth, deterministic pseudo-random field: the staple organic-motion source for effects.

Functions

Return Name Description
constexpr uint8_t inoise8 constexpr
constexpr uint8_t [inoise8] constexpr
constexpr uint8_t [inoise8] constexpr
constexpr uint16_t inoise16 constexpr 1D gradient noise at 16 bits. x is 16.16 fixed point: the whole part selects the cell, the fraction interpolates within it.
constexpr uint16_t [inoise16] constexpr 2D gradient noise at 16 bits: the common case for a panel.
constexpr uint16_t [inoise16] constexpr 3D gradient noise at 16 bits. z is the axis a 2D effect uses as time, so the field evolves in place instead of scrolling past.
uint16_t fbm16 inline Fractal Brownian motion at 16 bits: octaves samples at doubling frequency, halving amplitude.
uint16_t [fbm16] inline The same sum with a third axis, so a volumetric fixture samples a real field.
uint8_t fbm8 inline Fractal Brownian motion: octaves samples at doubling frequency, halving amplitude, returned normalized to 0..255. octaves=1 is plain noise; 3-4 is the usual cloud look.
uint8_t [fbm8] inline 3D fbm: the same sum with a z axis, so a 2D effect can use z as time for a field that evolves in place rather than scrolling past.
uint8_t turbulence8 inline Turbulence: fbm over |noise - 128|, which creases the field where it crosses the midpoint. The creases are what read as billowing smoke and flame rather than soft cloud.
uint8_t [turbulence8] inline 3D turbulence: the same creased sum with a z axis.
uint8_t warpImpl inline Domain warp: displace the sample coordinate by a noise field, then sample there. strength is how far the displacement reaches, in the same fixed-point units as the coordinates.
uint8_t warp8 inline 3D domain warp: the field flows through a volume rather than through a plane.
uint8_t [warp8] inline Displace the sample coordinate by a noise field, then sample there.
void curl16 inline Curl of a noise potential: a velocity field that cannot pile up or thin out.
void [curl16] inline The 2D form: the same field at a fixed z, which is what a panel wants.

inoise8

constexpr

constexpr constexpr uint8_t inoise8(uint32_t x)

inoise8

constexpr

constexpr constexpr uint8_t inoise8(uint32_t x, uint32_t y)

inoise8

constexpr

constexpr constexpr uint8_t inoise8(uint32_t x, uint32_t y, uint32_t z)

inoise16

constexpr

constexpr constexpr uint16_t inoise16(uint32_t x)

1D gradient noise at 16 bits. x is 16.16 fixed point: the whole part selects the cell, the fraction interpolates within it.


inoise16

constexpr

constexpr constexpr uint16_t inoise16(uint32_t x, uint32_t y)

2D gradient noise at 16 bits: the common case for a panel.


inoise16

constexpr

constexpr constexpr uint16_t inoise16(uint32_t x, uint32_t y, uint32_t z)

3D gradient noise at 16 bits. z is the axis a 2D effect uses as time, so the field evolves in place instead of scrolling past.


fbm16

inline

inline uint16_t fbm16(uint32_t x, uint32_t y, uint8_t octaves)

Fractal Brownian motion at 16 bits: octaves samples at doubling frequency, halving amplitude.


fbm16

inline

inline uint16_t fbm16(uint32_t x, uint32_t y, uint32_t z, uint8_t octaves)

The same sum with a third axis, so a volumetric fixture samples a real field.


fbm8

inline

inline uint8_t fbm8(uint32_t x, uint32_t y, uint8_t octaves)

Fractal Brownian motion: octaves samples at doubling frequency, halving amplitude, returned normalized to 0..255. octaves=1 is plain noise; 3-4 is the usual cloud look.


fbm8

inline

inline uint8_t fbm8(uint32_t x, uint32_t y, uint32_t z, uint8_t octaves)

3D fbm: the same sum with a z axis, so a 2D effect can use z as time for a field that evolves in place rather than scrolling past.


turbulence8

inline

inline uint8_t turbulence8(uint32_t x, uint32_t y, uint8_t octaves)

Turbulence: fbm over |noise - 128|, which creases the field where it crosses the midpoint. The creases are what read as billowing smoke and flame rather than soft cloud.


turbulence8

inline

inline uint8_t turbulence8(uint32_t x, uint32_t y, uint32_t z, uint8_t octaves)

3D turbulence: the same creased sum with a z axis.


warpImpl

inline

template<int Dims> inline uint8_t warpImpl(uint32_t x, uint32_t y, uint32_t z, uint16_t strength, uint8_t octaves)

Domain warp: displace the sample coordinate by a noise field, then sample there. strength is how far the displacement reaches, in the same fixed-point units as the coordinates.

3D domain warp: the same displacement with a z axis, so the field flows through a volume. Three probes, each offset by its own constant, so the axes displace independently.


warp8

inline

inline uint8_t warp8(uint32_t x, uint32_t y, uint32_t z, uint16_t strength, uint8_t octaves)

3D domain warp: the field flows through a volume rather than through a plane.


warp8

inline

inline uint8_t warp8(uint32_t x, uint32_t y, uint16_t strength, uint8_t octaves = 1)

Displace the sample coordinate by a noise field, then sample there.


curl16

inline

inline void curl16(uint32_t x, uint32_t y, uint32_t z, int32_t strength, int32_t & vx, int32_t & vy, uint32_t eps = 4096)

Curl of a noise potential: a velocity field that cannot pile up or thin out.


curl16

inline

inline void curl16(uint32_t x, uint32_t y, int32_t strength, int32_t & vx, int32_t & vy, uint32_t eps = 4096)

The 2D form: the same field at a fixed z, which is what a panel wants.

FadeTable

struct FadeTable
src/core/util/noise.h:149

The quintic fade as a table, because the polynomial needs a wide intermediate per sample.

Public Attributes

uint32_t v

Public Methods

constexpr inline constexpr FadeTable()

Tier16

struct Tier16
src/core/util/noise.h:182

Public Static Attributes

constexpr int kFrac = 16 : How many fraction bits a coordinate carries at this tier.

Public Static Methods

constexpr static inline constexpr uint32_t fade(int32_t f) : The quintic fade for this tier's fraction width.

constexpr static inline constexpr int32_t blend(int32_t a, int32_t b, uint32_t w) : Operands reach 2^18 against a 2^16 weight: a widening multiply (two on a 32-bit core).

Tier8

struct Tier8
src/core/util/noise.h:172

The two tiers as policies for the one core below: the fraction width, the fade of a fraction.

The blend a→b by a 0.16 weight sized to the tier's operand range.

Public Static Attributes

constexpr int kFrac = 8 : How many fraction bits a coordinate carries at this tier.

Public Static Methods

constexpr static inline constexpr uint32_t fade(int32_t f) : The quintic fade for this tier's fraction width.

constexpr static inline constexpr int32_t blend(int32_t a, int32_t b, uint32_t w) : Operands stay within ±2^11 (three offsets of a 256 cell), so the product fits 32 bits.

More info

The value varies smoothly across space, so neighboring coordinates give similar results, unlike a raw hash. Sample it across a grid for clouds, plasma or fire-like fields, and scroll a coordinate to animate. One gradient set and one interpolation serve all three dimensions.

The algorithm

This is Perlin's improved noise. A pseudo-random gradient sits at every lattice corner, chosen by a hash from the twelve cube-edge directions. Its dot product with the offset from the corner, a quintic fade on the fraction, and a linear blend across the corners give the value.

Gradient noise is zero at every lattice point and has no value bias toward the corners. That is why it reads as smooth and isotropic where value noise, which picks a random value per corner, reads blocky and axis-aligned at low frequency.

Two dimensions is three with the last offset at zero. One takes gradients of its own, since the cube-edge set projected onto a single axis is zero for six codes of sixteen. The cost scales with the corners: two, four and eight dot products.

Why the gradients are a table

A table rather than the usual select expression, because the selects compile to branches and a core without a branch predictor pays for each one. The select form was forty-four branches per two-dimensional sample on this target; the table is loads and small multiplies.

Why the core works a slice at a time

One instantiation per arity compiles to straight-line code over exactly its corners. A slice is two or four corners with the blends between them, and three dimensions is that slice at two depths blended by the depth fade.

One body with eight corners in flight spilled half its registers on a sixteen-register window, fifty-two stack stores a sample. The slice form keeps at most four.

Why the scale is per arity

Halving regardless is right for two and three dimensions and wrong for one, which then spans only the middle half of the range. Measured, a one-dimensional field covered the central half of the byte range and read washed out beside the same field sampled in two.

The three standard compositions

One sample is a smooth blur; the looks people recognize come from composing samples, and each composition is a few lines over the base rather than a new field generator.

Summing octaves at doubling frequency and halving amplitude turns the blur into cloud, terrain or smoke structure: large shapes with fine detail on them. The same sum over the absolute deviation creases the field, and those creases read as billows and flame. Sampling at a coordinate that noise itself displaced is the warp, which produces the flowing, marbled look.

Cost is stated per call because it decides whether an effect fits. Each octave is one sample, so three octaves cost three, and a warp costs its own probes plus the field it then samples. On a large fixture that multiplies by the pixel count.

What a domain warp is for

A warp displaces the sample coordinate by a noise field and then samples there, the strength saying how far that reaches in the same units as the coordinates. It is the primitive behind the flowing, marbled look. The field stops looking like a texture laid on the grid and starts looking like something moving through it, for two extra samples.

Why the volumetric warp takes three probes

It uses three rather than two, each offset by its own constant so the axes displace independently. Sampling one field three times would move everything along a diagonal.

Its octave count has no default, unlike the flat form's. With one, the two four-argument calls would both be viable and every existing call ambiguous, so requiring it makes the arity say which field the caller means.

Both share one body, so the two cannot drift apart. Each still compiles to exactly its own arity: the flat instantiation walks four corners and never touches the third axis. Calling the volumetric body with a zero third coordinate would have been simpler and measured nearly twice as slow. Every probe and the inner sum then walk eight corners to reach the same answer.

Why curl noise cannot clump

It is the perpendicular gradient of a scalar field, from Bridson's curl-noise paper. Taking the gradient of a potential and turning it a quarter turn gives a field whose divergence is zero by construction. Whatever flows into a region flows out again.

That is what separates it from sampling noise straight into a velocity, where the field has sources and sinks. Anything carried by such a field collects in the sinks and drains from the sources, which looks like clumping rather than flow.

The output is scaled by a strength in the caller's own units, and the sampling distance for the central difference is in the same coordinates as the field. Too small and the difference is quantization noise; too large and the curl is of a blurrier field than the one being sampled. The default is a sixteenth of a cell.

The output uses its full range

The dot product is signed offsets added, so the blend spans about a cell rather than the tighter bound a unit vector suggests. An exact search over every gradient choice at every fraction gives half a cell in one dimension, a cell in two and slightly more in three.

Each tier halves the raw blend onto its range, so the midpoint is the field's mean and both ends are reached. The clamp is a guard that only the three-dimensional extreme touches.

How a coordinate is read

A coordinate is fixed point scaled however the caller likes: its high byte selects the noise cell and its low byte the interpolation position within. A larger step per pixel is therefore finer noise, with more cells across the grid, and a smaller step is broader and smoother.

What the lattice hash costs

Each coordinate is multiplied by its own odd constant and the results xored, which shares work across a cell's corners: six multiplies per three-dimensional sample rather than twenty-four. Then one shift, one multiply, and the top nibble of the product, its best-mixed part.

Four bits is all a gradient needs, so that is the whole hash. A byte-wide avalanche spent three multiplies per corner on bits nothing read. Checked against it the sixteen codes are uniform to well under a percent, with adjacent corners as independent as the avalanche managed. A linear pre-mix with a single multiply was not: adjacent corners correlated six-fold, which reads as lattice patterns.

Both tiers share the hash, so the 16-bit field is the 8-bit one at finer resolution rather than an unrelated field.

The implementation is written fresh and integer throughout: the hash and the fade table are ours, and the gradient trick is Perlin's.