From Stateful to Stateless Blue Noise ends with a random number generator that stores nothing: the value at sample t is a hash away, and every aligned run of 2k samples lands once in each of 2k bins. That generator is one-dimensional because the question it answered was one-dimensional. The idea it came from was not.

01Back to Two Dimensions

2026-01-29 · a population tree in the plane
After many attempts at stacking 1-bit error-diffused sheets into a multi-bit dither mask, one works: dither the image at 50%, then dither each half of the pixels at 50% again on its own, and so on, eight levels deep. 255 full passes of kernel-switching error diffusion for an 8-bit mask. Each population is dithered with everyone else clamped to black or white, so the error travels across them.
2026-02-04 · the same tree in time
One dimension, one sample at a time: a binary tree of five-state ditherers. The blue noise generator used for token sampling.
2026-09-19 · the tree without state
Each node reduces to a random pair, 01 or 10, the pair's bit hashed from the node and the pair's number. No state, any sample directly.
2026-09-19 · and back
If a pair in time needs no memory, neither should a block in space.

The population tree is what both constructions share. A value is decided one bit at a time, most significant first. The pixels (or samples) that share the bits decided so far are a population, and each population splits itself evenly, on its own. What differs is how a population splits: by diffusing error, which needs a scan and a state, or by a rule that is exact in every small block, which needs neither.

02Pairs Become Blocks

In time, a node's two visits form a pair, and the pair is 01 or 10. In the plane the natural unit is the 2×2 block, and the natural rule is that its four pixels take four different values.

0

The finest scale decides the most significant digit. The four pixels of each 2×2 block receive the base-4 digits 0, 1, 2, 3 in an order drawn by hashing the block's coordinates. Any threshold that is a multiple of ¼ now cuts every block the same way: threshold ½ turns on exactly two of four pixels, everywhere.

1

Each population repeats it one scale up. The pixels whose first digit is 2 are a population with one member per 2×2 block. Group them by 4×4 block: four members, which receive the second digit in an order hashed from the digits so far and the 4×4 block. The other three populations do the same, each with its own order.

k

And so on. After k scales every aligned 2k×2k block holds each of its 4k values exactly once. Eight scales give a 16-bit value. The whole thing is a function of the seed and the pixel's coordinates.

Build the field, scale by scaleDetails
A 64×64 corner of the field, using only the first digits of each value. The grid marks the blocks of the last scale used. The check counts, over a 256×256 area, the aligned blocks of that size in which every value occurs exactly once.
The fields in the labs on this page are computed in your browser, pixel by pixel and frame by frame, by the same functions printed in section 06.
Values so far
Blocks with each value once
Block size
White noise would give

With one order per population and nothing from the block in the hash, every block of a scale is ordered alike and the field is a tile that repeats: a Bayer matrix with its entries shuffled. Hashing the block is what the pair's number was in time. It removes the period and leaves the blocks exact.

A different grid for each population

The offset version of the 1D generator extends to the plane too. Each population chooses one of four offsets: (0,0), (1,0), (0,1) or (1,1), hashed from its value prefix, the level and the seed. Add that offset to its current coordinates, divide each coordinate by two with floor rounding, and use the remainders to identify a cell in the shifted 2×2 group. A permutation hashed from the population and the group assigns the next digit. The group coordinates become the coordinates for the next level.

This works because every group sends exactly one member to each child population. Each child therefore has one member at every position of its own coordinate lattice, which can be shifted and grouped again without inspecting neighbouring pixels. Evaluation remains stateless. Each population retains one member per cell of its own shifted hierarchy, but different populations no longer share one grid: an aligned square need not contain every value bin once.

The root still has a single shifted 2×2 grid. At thresholds ½ and ¼ only that first split matters, so the offset changes the alignment; deeper offsets affect finer thresholds. The labs below include this experimental form. Its spectra can be inspected live, but a systematic comparison of grain, directional artifacts and sliding-window error remains to be done.

03Cutting the Field

A dither mask is judged by what its thresholds look like. Blocks that always hold the right number of dots cannot clump at the scale of a block or above, so the power spectrum of a cut has no low frequencies to speak of: it rises as f², +6 dB per octave, in every direction. That is the pair's spectrum, turned around an axis.

One threshold, six fieldsDetails
256×256 pixels, on where the field is under the threshold. The plot is the radially averaged power spectrum of that cut, relative to its own mean, against a +6 dB per octave guide. The slope is fitted from 0.01 to 0.1 cycles per pixel, low frequencies are those under 1⁄32, and the spread is the difference between the strongest and weakest of eight directions between 0.03 and 0.08. The population tree is the original 8-bit map of 2026-01-30, not a reconstruction (255 passes of error diffusion, tileable); it has 256 levels, so its thresholds step by 1⁄256.
Radial slope
Low frequencies
Spread over directions
Aligned 4×4 blocks with the exact count

The two trees are the same kind of picture at two levels of smoothness. The error diffused one keeps its dots apart: its spectrum has a knee and then a plateau, and it reads as finer grain. The stateless one has the ideal ramp, is the same in every direction to about a decibel, and has the exact count in every block, but nothing stops two dots from touching across the border of two blocks, and it reads as coarser. Reading the one-dimensional generator along a Z curve keeps the blocks and loses the directions: at threshold ½ its pairs are all horizontal.

04Adding Time

The obvious extension treats the frame number as a third coordinate and orders the eight pixels of each 2×2×2 block. It fails, and the reason is worth a sentence. A block that is balanced as a whole is not balanced slice by slice: four of its eight pixels are on, but a single frame may hold three of them, or none. One frame of such a field, and one pixel's timeline, both measure as white noise.

What is needed is a field in which every frame is a field of section 02 and every pixel's timeline is the generator of the previous article. At one scale that means: within a 2×2 block, across four consecutive time slots, each pixel takes each of the four digits once (time), and in each slot the four pixels take four different digits (space). A 4×4 array with each digit once in every row and every column is a Latin square.

Rows are the four pixels of a block, columns four consecutive time slots. Each digit once per row: the pixel is exact over four slots. Each digit once per column: the block is exact in every slot. In every row the first two entries are one low digit and one high one, so threshold ½ alternates within each pair of slots, as the pair does.

At scale j a slot is 4j frames long and the block is 2j+1 pixels wide, and the square is hashed from the digits so far, the block in space and the block of four slots in time. Of the 576 Latin squares of order 4, half have the property in the caption's last sentence. With those, every pixel's timeline is exact in every aligned run of 2, 4, 8, 16, … frames.

Exact apart is not exact together

Repeating the Latin square scale by scale gives a field in which every frame is exact and every pixel is exact, and an earlier version of this draft stopped there. The dots give it away. At most thresholds they walk around a small rectangle and fill it. The digit of scale j depends only on which sub-block a pixel is in and which slot a frame is in, so everything inside a sub-block shares it for the whole of a slot, and rounds the same way. Count the lit pixels of an 8×8 block over 64 frames and the result is no better than white noise, although every one of its pixels and every one of its frames is exact. The mean of one pixel over N frames cannot see this, and a posterized mean always wins that measure.

The condition that was missing is the joint one: a small block over a short run of frames has to hold each value once as well. The three conditions together are the definition of a net, here in base 4 over three coordinates: place (one digit per scale, the 2×2 cell), time (one digit per scale, the slot) and value. Faure's construction, in the form Niederreiter gave it for bases that are powers of a prime, gives such a net for any number of scales, from Pascal's triangle taken modulo 2 and the field of four elements. Read as a function it is two matrix products, value = MS·place + MT·time, and what makes it work is that the matrices are not diagonal: the most significant digit of the value still comes from the finest cell and the finest slot, and every later digit mixes in the finer ones along with its own. Reordering the digits of each coordinate by a hash of the coarser digits, which is Owen's scrambling, keeps every box exact and removes the regularity. With one scale, the net is a Latin square.

Four fields, frame by frameDetails
96×96 pixels of each field at the same threshold. Under each panel: the marked pixel's last 64 frames, newest on the right. Beside each name, two measures since the last reset: how many aligned runs of 16 frames held the exact number of lit frames, over the pixels of the marked 8×8 area; and the error of the lit count in boxes of 4×4 pixels over 16 frames, across the whole panel, against what white noise at the same density would give. Click a panel to move the mark.
Net
Latin squares
Frames independent
White noise
frame 0exact runs of 16 frames · 4×4×16 boxes against white

The first three panels look the same in any one frame. They differ in how they change. With independent frames a pixel that was lit is as likely as any other to be lit again; with the other two it has just used up its turn. The net and the Latin squares differ in the second number: at a threshold such as 0.2 the Latin squares' boxes are as uneven as white noise, and the net's are 20 dB and more under it. At ½ and ¼ all three are exact in every box.

05A Live Dither Mask

The field is already the full population split: sixteen bits per pixel per frame, every threshold of it a pattern, the patterns nested. Used as a dither mask, a pixel is lit when the image is brighter than the mask. Because every pixel's timeline is exact (with the net and with the Latin squares alike), a pixel of brightness g is lit in exactly ⌊g·N⌋ or ⌈g·N⌉ of any aligned run of N = 4k frames. The average of N frames is therefore the image itself, rounded to N + 1 levels, and its error falls as 1⁄N. Independent masks, however good each one is, average as noise does: 1⁄√N.

The Population offsets · independent frames option uses the spatial construction of section 02, with a new seed for every frame. It lets you compare its spatial grain and accumulation against the common-grid independent frames. It has no temporal balancing or exact space-time boxes; its temporal averaging is expected to follow the independent-frame 1⁄√N rate. Pause to inspect a single frame, or select the 4×4 block error to compare spatial averaging.

Dither an image, watch the frames add upDetails
Left: the current frame at one bit per channel. Middle: the mean of the frames since the last reset. Right: the source. One-bit pixels mix as physical light, so the image is converted from sRGB to linear light before it is compared with the mask, the lit frames are counted per pixel, and the count, a quantity of light, is encoded back to sRGB for the screen. The mean is always shown that way, because that is what the frames add up to. Switch the quantizing to the stored values and the mean comes out too bright: the frames then add up to the wrong light. The error curves always use linear light. The artwork was resized with CRA's Catmull-Rom filter, which also works in linear light. Below: the RMS error of the mean against the source for the five masks, measured on the gray image, as the frames accumulate, either pixel by pixel or after averaging 4×4 blocks, which is closer to what an eye or a filter does. Your own image stays in your browser.
This frame
Mean so far
Source8 bits
RMS error of the mean against the sourceframes, log scale
netLatin squaresframes independentpopulation offsets, independent frameswhite noise1⁄N and 1⁄√N guides

Pixel by pixel the net and the Latin squares are the same curve. Averaged over 4×4 blocks they part: the Latin squares' rounding is shared by the pixels of a block and does not average away, the net's does. Either may be the right tool. A process that integrates each pixel on its own, and nothing else, loses nothing with the Latin squares and their one table-free square per scale; anything that also averages over neighbours wants the net.

The exactness has a look of its own. After four frames the mean has five levels and the pixels of a block have rounded the same way: it is posterized where the others are noisy. Whether that is preferable depends on what does the averaging. A display that shows the frames to an eye, a sensor that integrates them, and a renderer that accumulates them are three different answers.

06The Functions

The net first. The matrices are built once, from Pascal's triangle modulo 2 and powers of an element of the field of four, and one small matrix is inverted. They are kept as bit masks: a digit of that field is two bits, multiplying it by a constant is linear in those bits, so a row of a matrix times a vector of digits is two parities of a few ANDs. After that a value costs three hashes per scale and no arithmetic to speak of. Computed this way it is faster than the Latin squares below.

/*
 * A stateless dither mask for every pixel and every frame: exact in every block of pixels over
 * every run of frames.
 * From "Stateless Blue Noise in Space and Time" by Jan Boon (Kaetemi),
 * https://polyverse.dev/articles/bluenoise-spacetime.html
 * Implementations derived from this code or from that article must keep this reference and
 * this notice.
 */
#include <stdint.h>

/* lowbias32, from Chris Wellons's hash-prospector (public domain). The constants are the ones
 * TheIronBorn posted in issue 19 of that repository on 2022-05-07:
 * https://github.com/skeeto/hash-prospector/issues/19#issuecomment-1120105785 */
static inline uint32_t lowbias32(uint32_t x) {
    x ^= x >> 16; x *= 0x21f0aaadu;
    x ^= x >> 15; x *= 0x735a2d97u;
    x ^= x >> 15; return x;
}

static const uint8_t ORDER[24][4] = {                   /* the 24 orders of 0..3 */
    {0,1,2,3},{0,1,3,2},{0,2,1,3},{0,2,3,1},{0,3,1,2},{0,3,2,1},
    {1,0,2,3},{1,0,3,2},{1,2,0,3},{1,2,3,0},{1,3,0,2},{1,3,2,0},
    {2,0,1,3},{2,0,3,1},{2,1,0,3},{2,1,3,0},{2,3,0,1},{2,3,1,0},
    {3,0,1,2},{3,0,2,1},{3,1,0,2},{3,1,2,0},{3,2,0,1},{3,2,1,0} };

/* The field of four elements: 0, 1, w = 2, w + 1 = 3. Addition is XOR. */
static const uint8_t GMUL[4][4] = { {0,0,0,0},{0,1,2,3},{0,2,3,1},{0,3,1,2} }, GINV[4] = { 0,1,3,2 };

typedef struct { uint32_t seed; int m; uint32_t mask[16][8]; } blue_net;      /* m scales, 1..16, two bits of value each */

/* Faure's matrices: entry (r, c) of the Pascal matrix modulo 2, times z^(c - r). */
static uint8_t faure(int z, int r, int c) {
    if (c < r || (c & r) != r) return 0;
    if (c == r) return 1;
    if (z == 0) return 0;
    uint8_t e = 1; for (int k = 0; k < c - r; k++) e = GMUL[e][z]; return e;
}

void blue_net_init(blue_net *f, uint32_t seed, int m) {
    uint8_t a[16][32] = {{0}}, mt[16][16], ms[16][16];
    f->seed = lowbias32(seed + 0x9e3779b9u); f->m = m;
    /* invert the block of the time matrix that acts on the unknown half of the point index */
    for (int r = 0; r < m; r++) { for (int c = 0; c < m; c++) a[r][c] = faure(1, r, m + c); a[r][m + r] = 1; }
    for (int col = 0; col < m; col++) {
        int p = col; while (!a[p][col]) p++;
        for (int c = 0; c < 2 * m; c++) { uint8_t s = a[col][c]; a[col][c] = a[p][c]; a[p][c] = s; }
        uint8_t iv = GINV[a[col][col]]; for (int c = 0; c < 2 * m; c++) a[col][c] = GMUL[iv][a[col][c]];
        for (int r = 0; r < m; r++) if (r != col && a[r][col]) { uint8_t g = a[r][col]; for (int c = 0; c < 2 * m; c++) a[r][c] ^= GMUL[g][a[col][c]]; }
    }
    /* value = ms * place + mt * time */
    for (int r = 0; r < m; r++) {
        for (int k = 0; k < m; k++) { uint8_t g = 0; for (int c = 0; c < m; c++) g ^= GMUL[faure(2, r, m + c)][a[c][m + k]]; mt[r][k] = g; }
        for (int c = 0; c < m; c++) { uint8_t e = faure(2, r, c); for (int k = 0; k < m; k++) e ^= GMUL[mt[r][k]][faure(1, k, c)]; ms[r][c] = e; }
    }
    /* as bit masks: multiplying a two-bit digit by a constant is linear in its bits */
    for (int r = 0; r < m; r++) { for (int q = 0; q < 8; q++) f->mask[r][q] = 0;
        for (int c = 0; c < m; c++) for (int q = 0; q < 2; q++) { uint8_t g1 = q ? mt[r][c] : ms[r][c], gw = GMUL[g1][2]; uint32_t *o = f->mask[r] + 4 * q;
            o[0] |= (uint32_t)(g1 & 1) << c; o[1] |= (uint32_t)(gw & 1) << c; o[2] |= (uint32_t)(g1 >> 1) << c; o[3] |= (uint32_t)(gw >> 1) << c; } }
}

static inline uint32_t parity32(uint32_t x) { x ^= x >> 16; x ^= x >> 8; x ^= x >> 4; return (0x6996u >> (x & 15)) & 1; }
static inline const uint8_t *order(uint32_t h) { return ORDER[((h >> 16) * 24) >> 16]; }

/* The mask value of pixel (x, y) in frame t, 2 * m bits. Light the pixel when image > value. */
uint32_t blue_net_at(const blue_net *f, uint32_t x, uint32_t y, uint32_t t) {
    int m = f->m;
    uint32_t region = lowbias32(lowbias32((m < 32 ? x >> m : 0) + (m < 32 ? y >> m : 0) * 0x9e3779b1u)
                    ^ lowbias32((2 * m < 32 ? t >> (2 * m) : 0) + 0x85ebca6bu) * 0xc2b2ae35u ^ f->seed);
    uint32_t s0 = 0, s1 = 0, t0 = 0, t1 = 0, pre, v = 0;
    pre = 1; for (int k = 0; k < m; k++) {                              /* place: 2x2 cells, coarsest first, reordered by the coarser cells */
        int j = m - 1 - k; uint32_t d = ((y >> j) & 1) * 2 + ((x >> j) & 1), o = order(lowbias32(region ^ pre * 0x2545f491u ^ 0x1111u))[d];
        s0 |= (o & 1) << k; s1 |= (o >> 1) << k; pre = pre * 4 + d; }
    pre = 1; for (int k = 0; k < m; k++) {                              /* time: base-4 digits of the frame number, the same way */
        uint32_t d = (t >> (2 * (m - 1 - k))) & 3, o = order(lowbias32(region ^ pre * 0x2545f491u ^ 0x2222u))[d];
        t0 |= (o & 1) << k; t1 |= (o >> 1) << k; pre = pre * 4 + d; }
    pre = 1; for (int k = 0; k < m; k++) {                              /* value digits, most significant first */
        const uint32_t *K = f->mask[k];
        uint32_t a = parity32((K[0] & s0) ^ (K[1] & s1) ^ (K[4] & t0) ^ (K[5] & t1)) | parity32((K[2] & s0) ^ (K[3] & s1) ^ (K[6] & t0) ^ (K[7] & t1)) << 1;
        v = v * 4 + order(lowbias32(region ^ pre * 0x2545f491u ^ 0x3333u))[a]; pre = pre * 4 + a; }
    return v;
}

An area of 2m pixels square over 4m frames is one net, exact in every box inside it, and scrambled independently of its neighbours. For a still mask, hold t at zero. For several colour channels, read the same field at places far apart.

The Latin squares, one scale at a time

Exact for every pixel and for every frame, but not for the two together (section 04). A Latin square does not need a table. For any three orders A, B, P of the numbers 0 to 3, the array P[A[row] XOR B[column]] is a Latin square. The first two entries of every row straddle 2 whenever B[0] XOR B[1] differs from the XOR of the two places where P is below 2; when it does not, swapping B[1] and B[2] repairs it. The only constant is the list of the 24 orders of four things.

/*
 * A stateless dither mask for every pixel and every frame, one Latin square per scale: exact
 * for each pixel and for each frame.
 * From "Stateless Blue Noise in Space and Time" by Jan Boon (Kaetemi),
 * https://polyverse.dev/articles/bluenoise-spacetime.html
 * Implementations derived from this code or from that article must keep this reference and
 * this notice.
 */
#include <stdint.h>

/* lowbias32, from Chris Wellons's hash-prospector (public domain). The constants are the ones
 * TheIronBorn posted in issue 19 of that repository on 2022-05-07:
 * https://github.com/skeeto/hash-prospector/issues/19#issuecomment-1120105785 */
static inline uint32_t lowbias32(uint32_t x) {
    x ^= x >> 16; x *= 0x21f0aaadu;
    x ^= x >> 15; x *= 0x735a2d97u;
    x ^= x >> 15; return x;
}

static const uint8_t ORDER[24][4] = {                   /* the 24 orders of 0..3 */
    {0,1,2,3},{0,1,3,2},{0,2,1,3},{0,2,3,1},{0,3,1,2},{0,3,2,1},
    {1,0,2,3},{1,0,3,2},{1,2,0,3},{1,2,3,0},{1,3,0,2},{1,3,2,0},
    {2,0,1,3},{2,0,3,1},{2,1,0,3},{2,1,3,0},{2,3,0,1},{2,3,1,0},
    {3,0,1,2},{3,0,2,1},{3,1,0,2},{3,1,2,0},{3,2,0,1},{3,2,1,0} };

typedef struct { uint32_t key[16]; int scales; } blue_field;   /* scales: 1..16, two bits of value each */

void blue_field_init(blue_field *f, uint32_t seed, int scales) {
    f->scales = scales;
    for (uint32_t j = 0; j < 16; j++) f->key[j] = lowbias32(seed + (j + 1) * 0x9e3779b9u);
}

/* The mask value of pixel (x, y) in frame t, 2 * scales bits. Light the pixel when image > value. */
uint32_t blue_field_at(const blue_field *f, uint32_t x, uint32_t y, uint32_t t) {
    uint32_t v = 0;
    for (int j = 0; j < f->scales; j++) {
        uint32_t s   = ((y >> j) & 1) * 2 + ((x >> j) & 1);             /* which pixel of the block */
        uint32_t tau = (t >> (2 * j)) & 3;                             /* which time slot */
        uint32_t bt  = (2 * j + 2 < 32) ? t >> (2 * j + 2) : 0;        /* which block of four slots */
        uint32_t h   = lowbias32(lowbias32(f->key[j] ^ v)                /* the population */
                     ^ lowbias32((x >> (j + 1)) + (y >> (j + 1)) * 0x9e3779b1u)   /* the block in space */
                     ^ lowbias32(bt + 0x85ebca6bu) * 0xc2b2ae35u);      /* the block in time */
        uint32_t h2  = lowbias32(h ^ 0x68bc21ebu);
        const uint8_t *A = ORDER[((h & 0xffff) * 24) >> 16], *B = ORDER[((h >> 16) * 24) >> 16], *P = ORDER[((h2 >> 16) * 24) >> 16];
        uint32_t lo0 = 0, lo1 = 0;                                     /* where P is 0 and where it is 1 */
        for (uint32_t i = 0; i < 4; i++) { if (P[i] == 0) lo0 = i; if (P[i] == 1) lo1 = i; }
        uint32_t b1 = B[1], b2 = B[2];
        if ((B[0] ^ b1) == (lo0 ^ lo1)) { b1 = B[2]; b2 = B[1]; }      /* keep each pair of slots one low, one high */
        uint32_t b = tau == 0 ? B[0] : tau == 1 ? b1 : tau == 2 ? b2 : B[3];
        v = v * 4 + P[A[s] ^ b];
    }
    return v;
}

Two hashes of the population and blocks and one more for the third order: three rounds of hashing per scale, and as many scales as half the bits wanted.

07Measurements

One value field per row, cut at four thresholds. Slope in dB per octave, low frequencies against the mean in dB, spread over directions in dB.

In the plane, 1024²Blocks exact1⁄21⁄40.30.1
Quadtree, statelessall, 2×2 to 32×32+6.0 · −28 · 0.4+5.9 · −28 · 0.6+4.8 · −22 · 0.4+4.9 · −18 · 0.4
Population tree of error diffusions (the original, 256²)42% of 2×2+3.9 · −32 · 7.9+3.6 · −29 · 7.1+4.7 · −26 · 2.7+6.5 · −25 · 3.6
1D generator along a Z curveall+6.3 · −30 · 13.3+6.1 · −28 · 0.3+5.1 · −22 · 0.7+4.7 · −18 · 4.7
Eight independent error-diffused sheets29% of 2×2+4.8 · −33 · 1.6white below 1⁄2
White noise9% of 2×20.0 · 0 · 0.5
In space and time, 256² × 1024 framesEach frameEach pixel's timeline
Netevery block exact · +6.1 · +6.1 · +5.3 · +4.5 at 1⁄2, 1⁄4, 0.3, 0.1every run of 4, 16, 64 frames exact, runs of 2 two times in three · +5.8 · +6.0 · +3.0 · +2.2
Latin squaresevery block exact · +6.1 · +6.2 · +5.0 · +4.8every run of 2, 4, 16, 64 frames exact · +6.0 · +5.8 · +2.8 · +2.6
Latin squares without the pair propertythe sameruns of 2 exact 67%, of 4 and up all
Frames independentevery block exact · +6.0 · +5.8 · +4.9 · +4.5white
2×2×2 blockswhitewhite

The error of the lit count in a box, against white noise at the same density, in dB, at threshold 0.2. The pattern is the same at 0.3, 0.1 and 0.04; at ½ and ¼ the first two rows are exact in every box, and the third in every box that covers a 2×2 block of pixels or more.

BoxOne pixel, 4 · 16 · 64 framesOne frame, 4×4 · 8×82×2×44×4×44×4×168×8×88×8×64
Net−6 · −12 · −18−12 · −18−12−18−24−25−35
Latin squares−6 · −12 · −18−12 · −180−60−90
Frames independent0 · 0 · 0−12 · −18−6−12−12−18−19
White noise0
RMS error of the mean of N frames, each pixel, test image141664256
Net0.4040.1010.0260.00640.0016
Latin squares0.4040.1010.0260.00650.0016
Frames independent0.4040.2020.1010.05020.0255
White noise0.4070.2030.1010.05060.0252

08Limits and Open Questions

  • Synchronized inversions in the net. At threshold ½, every frame inside one net region is either the same spatial mask or its complement. Pixels throughout that region share a temporal sequence or its inverse; a frame transition changes all of them or none. At ¼ there are only four possible masks. This is stronger structure than the local walking described below. The first value digit separates into a spatial contribution XOR a temporal contribution, followed by a permutation fixed for the region; the finer digits cannot change these threshold decisions. With eight scales the region is 256×256 pixels over 65,536 frames. The blue slopes measured separately in space and time, and the exact joint counts, do not establish satisfactory spatiotemporal blue noise. Removing this synchronized behavior remains unresolved.
  • The grid keeps time. Every block of a scale is dealt again on the same frame. At threshold ¼, half of the pixels that are lit change on three frames out of four and a third fewer on the fourth, and the light of a lit pixel moves to a pixel of its own 2×2 block more often than chance (0.31 against 0.25). That is the walking, and it comes with exact aligned blocks, with the net and the Latin squares alike. Masks optimised for space and time do not have it, and have no exact blocks.
  • Runs of 2, 8, 32 frames. The net is an object in base 4, exact in runs of 4, 16, 64 frames. The Latin squares, with the pair property, are exact in runs of 2, 8, 32 as well. Giving the net the same property looks possible (hold the value orders of a digit to a common low pair, and choose the time orders against it) and has not been done.
  • Beyond one net. Boxes are exact inside an area of 2m pixels square over 4m frames and not across its borders. With eight scales that is 256 pixels and 65,536 frames.
  • It is first-order blue. Power rises from zero as f², with no ring at a principal frequency, because nothing keeps dots apart across the border of two blocks. A void-and-cluster mask or error diffusion looks finer. The procedural field avoids storing a threshold texture; it still uses fixed configuration data and finite coordinate words. Its exactness applies to the specified aligned boxes, and does not imply an infinite nonrepeating field.
  • Thresholds that are not a power of one half. In the plane the slope holds up well (+4.8 at 0.3). In time it falls to about +2.8, as it does for the one-dimensional generator, because a level deep in the tree acts on a time scale 4j frames long.
  • Memory across blocks. The five-state machine improved on the pair by carrying error from one pair to the next. A block that knows something about its neighbours might do the same in the plane, at the price of no longer being a pure function of one pixel. Not tried.
  • Channels. The RGB demo reads one field at three distant places. Whether anticorrelated channels (for steadier luminance) are better has not been measured.
  • The hash. Everything rests on a 32-bit hash used three times per scale. Its quality has only been tested through the spectra and the strata.
  • Displays. Temporal dithering in display drivers is usually a short fixed pattern. Whether exact runs and a blue timeline are worth three hashes per scale per pixel per frame in that setting is a question for someone who builds them.
  • Prior work. Hierarchical ordering, the 24 base-four permutations, digital-net inversion and balanced temporal cycles all have relevant precedents. The comparison notes below distinguish those building blocks from the specific population-offset recursion and position–time–value mapping. Their full relationship to existing methods remains under review.

Future exploration: generative diffusion

Temporal sampling is the interesting connection. In rendering, successive samples estimate the same underlying pixel value; distributing them well over time can make the accumulated frame converge much faster. The dither demo above has an especially simple version of that property. Generative diffusion instead changes the state at every denoising step. A perturbation changes what the next denoiser evaluation sees, so a later, opposite perturbation need not undo its effect. The 1⁄N result above does not transfer to diffusion steps.

The question to explore is whether structured randomness along a generation trajectory can produce comparable image quality, prompt adherence and diversity with fewer denoiser evaluations. Spatial blue noise already has a precedent: Blue noise for diffusion models (2024) changes both training and sampling to use a changing noise covariance. Colored Noise Diffusion Sampling (2026 preprint) instead proposes a training-free stochastic sampler with time- and frequency-dependent noise injection. Neither establishes a benefit from the generators on this page.

  • Spatial noise at inference. Keep an existing model's usual starting noise and compare white noise, filtered Gaussian noise and the procedural 2D fields for subsequent stochastic injections. Measure the effect of the noise schedule as well as the field. A deterministic sampler has no repeated noise injection to replace.
  • Correlations between denoising steps. Test spatial-only structure separately from temporal structure. Balancing the raw noise sequence changes the usual independent-increment process; a useful construction may need to account for changing noise strengths and the denoiser's response. The current net's synchronized inversions remain an unresolved defect, not evidence of useful cancellation.
  • Several perturbations at one state. Balanced samples could estimate an explicitly defined noise-averaged denoiser prediction. This is closer to rendering's repeated estimates of one quantity, but it changes the update and costs additional network evaluations. Compare at equal evaluation budgets, not merely equal step counts.
  • Training. A model could be trained with a matching structured corruption process and sampler. A smaller experiment is to use the 1D generator to distribute training noise levels across examples, leaving the image noise unchanged. Low-discrepancy timestep sampling already reduces estimator variance in Variational Diffusion Models (2021); the comparison here should include ordinary stratification.
  • Video time is a separate axis. Correlating noise between video frames addresses motion and visual continuity, rather than progress through denoising steps. Temporal anticorrelation at fixed pixels need not help either: compare shared or motion-aligned noise as well as independent frames.

These fields supply uniform ranks. An inverse Gaussian CDF gives approximately Gaussian marginals, but does not make the field jointly Gaussian or preserve its spectrum. Measure the transformed noise and its correlations before using it in a diffusion process. All of these applications remain untested here; the practical opportunity is a directly addressable noise source without stored masks, and the open question is which structure actually helps generation.

Attribution

Implementations derived from this article, in any language, must keep a reference to it: the address https://polyverse.dev/articles/bluenoise-spacetime.html, credit to Jan Boon (Kaetemi), and this requirement itself, so that the reference is carried into whatever is derived from them in turn.

09What Is New Here, and What Is Not

Part by part, against the sources in the reading list, with the labels of the generators article and one more. Existing: existing art, whether used knowingly or arrived at independently and then matched to a source. Variant: an existing construction with one stated change. Combination: existing parts put together in a way not found in the sources reviewed. New: no counterpart found in the sources reviewed. Result: a measurement or derivation about the objects on this page; a fact about them, not a technique. Implementation: code for a construction on this page; only its correctness is claimed. Nothing claimed: questions, with no experiment. "Not found" means not found in the sources listed below; the reading list says which were read in full.

The closest work depends on which part of the construction is being compared. A procedural spatial threshold, a rendering sampler and a temporal display pattern can share an ordering rule while measuring quite different errors. These notes separate the known building blocks from the particular combinations on this page. They are a working comparison, not a claim that the search is complete.

Vegdahl's base-four pseudocode chooses a permutation from the higher original Morton bits. Fil's spatial function builds its value from fine to coarse with coordinate-dependent flips. The common-grid function here additionally uses the value prefix accumulated so far. That is a concrete dependency to compare, rather than evidence that hierarchical ordering or the 24 permutations are new.

ART-Owen distributes scrambling information through a grammar; Q-ART realizes the 24 permutations with six invertible binary matrices and four XOR translations. Those translations act on digit codes. The population-offset experiment instead shifts each population's current lattice, divides it into groups, and passes the group coordinates onward. That exact recursion has not been identified in the sources reviewed here. Establishing equivalence would require matching the complete mapping, including its inverse and the loss of the common-grid counting guarantee.

Grünschloß, Raab and Keller describe solving for index digits from coordinate prefixes, including Faure constructions and the use of remaining coordinates in rendering. In the notation here, fixing place gives the first index block; time determines the rest. Substitution gives:

Index eliminationaR = TR−1(τ + TLs)
w = VLs + VRaR = MSs + MTτ

All operations here are in GF(4). Precomposing the matrices is ordinary algebra, and the exact prefix counts come from the chosen net and the permutations that preserve its boxes. The specific object to compare is the whole finite construction: one coordinate for hierarchical pixel position, one for frame number, and one returned as the image threshold. Calling the last coordinate a threshold does not establish a mathematical distinction on its own.

A quick visual check suggested that the population-offset field's local spectra varied less between spatial windows. That has not yet been established by a systematic experiment. NILE §5.4 and Figure 10 address a related objective: consistent spectral quality across successive sample blocks. Moving a window over a spatial threshold field is a different test.

  1. Three arms. Compare the common-grid field, that same field with only the offset form's root translation, and the full population-offset recursion. Use the same seeds, precision and window sizes.
  2. Thresholds and phases. Test 3/8, 5/16, 0.3 and 0.1, sampling positions across the phases of the relevant grids. Keep ¼ and ½ as controls: deeper offsets cannot affect those slices.
  3. Measure variation and quality separately. Report the mean and between-window variation of a fixed low-frequency energy ratio. Measure angular anisotropy on the two-dimensional spectrum before radial averaging. Repeat across independent seeds; overlapping windows are not independent trials.
  4. Keep the counting trade-off visible. Measure occupancy error separately. The common-grid construction has exact aligned prefix counts; the offset construction preserves representatives on each population's own lattice. Lower variation alone could mean uniformly worse windows.

US9583072B2 describes combining a repeated pseudo-random value with a frame counter so each pixel visits all threshold levels within a frame group. US8928688B2 describes spatial and temporal dithering patterns that account for subpixel driving polarity. These are useful technical references for balanced cycles. They do not, merely by sharing that purpose, establish that the particular prefix-dependent Latin-square recursion and pair correction here are the same method.

A useful experiment should compare per-pixel cycles, spatial counts and joint boxes separately, then inspect the filtered animation. No speed, spectral or rendering-quality advantage over these related methods has been established here by a common benchmark.

11To Do Before Publication

This section goes when the list is done

The net is an unfinished candidate for spatiotemporal blue noise. Its aligned strata and measured spectral slopes remain useful results, but the synchronized inversions must be addressed and the comparison below completed before presenting it as a working spatiotemporal blue-noise mask.

  • The spatial offset variant. Compare the common-grid and per-population-offset fields over multiple seeds, thresholds and window positions. Measure spectral shape, anisotropy and filtered error. The live dither demo redraws the offset field independently each frame; coupling it across time is a separate, unresolved construction.
  • Resolve the synchronized inversions. Measure cross-pixel temporal correlations and repeated or complementary frames within a net region, especially at ½ and ¼. Investigate more local randomization while tracking which joint stratification guarantees survive. Check shifted windows and moving content as well as aligned box counts; a blue slope alone does not resolve this failure.
  • The primary comparison: spatiotemporal blue noise masks. Wolfe, Morrical, Akenine-Möller and Ramamoorthi, "Scalar Spatiotemporal Blue Noise Masks" (arXiv:2112.09629, EGSR 2022): void and cluster with an energy that links pixels of the same frame and the same pixel across frames, stored as textures such as 64×64×64. Run their published masks and this function through one protocol, theirs plus ours:
    • the spectrum of the frames (their averaged DFT over x and y) and of time (their DFT over x and t), and our per-frame radial slope and per-pixel slope;
    • their Monte Carlo test: RMS error of integrating a ramp, a step and a sine with 64 samples, per pixel over time;
    • their accumulation test: an exponential moving average with α = 0.1, looked at after 4 and after 64 frames;
    • our box errors (pixel, frame, and boxes of pixels over frames) and the mean of N frames, per pixel and per 4×4 block;
    • their baselines: white noise, an independent 2D blue noise mask per frame, 3D blue noise, a 2D mask animated by the golden ratio, and an R2-offset texture.
    Test grain, convergence under the chosen filter, and memory and computation costs without assuming a winner. The procedural field avoids a stored threshold texture, but still uses fixed permutation and matrix data and has finite regions and coordinates.
  • A rendering test. One sample per pixel per frame with temporal accumulation and with a TAA-style filter, on something with real content (ambient occlusion or stochastic transparency, as in that paper), seeded from this field and from theirs.
  • Moving content. The exact counts hold while a pixel's value stands still over a run. Measure what is left when the image moves, fades or pans, and whether the lockstep of the grid shows.
  • The single frame. Set a frame beside a void-and-cluster mask, spectrum and picture. If the grain matters for a use, try memory across blocks, or a finest scale taken from a small stored mask.
  • The rhythm. Measure the visibility of every block being dealt again on the same frame, and try the offsets that keep boxes exact inside a region while desynchronising regions.
  • Runs of 2, 8, 32 frames for the net. The pair property, as a legitimate scrambling (section 08).
  • The hash. A proper test of the three hashes per scale, more seeds, and a 64-bit frame number.
  • A shader. GLSL or HLSL, timed on a GPU, checked against the C code value for value.
  • Colour. Measure luminance flicker for three far-apart reads against anticorrelated channels.
  • Related-work follow-up. Complete the matrix and coordinate comparison, including NILE’s concrete elements; review the expanded Q-ART paper and the remaining shader, halftoning and display references. Confirm the outstanding bibliographic details. Add matched implementations and benchmark results before drawing conclusions about advantages.

12Reading List

The list starts with spatial relatives and continues through mask optimisation, nets and display methods. The additional references at the end support section 10. A shared purpose or guarantee does not establish identical constructions, and an incomplete search does not establish novelty. Source-specific reading notes distinguish reviewed passages from work still to check.

Closest: stateless scrambled Bayer functions

  1. Job van der Zwan, "Pseudo-Blue Noise," and Fil (Philippe Rivière), "Pseudoblue," Observable notebooks. Fil's direct per-pixel function flips the cell bits using a hash of the parent block, placing the finest-scale digit first. It credits van der Zwan's earlier whole-map construction and a shader by NuSan. The comparison here concerns Fil's function: four coordinate-dependent flips rather than a population-dependent choice among 24 permutations.
  2. Nathan Vegdahl, "Owen Scrambling Based Blue-Noise Dithered Sampling," 2022, and "A Fast Hash for Base-4 Owen Scrambling." A pixel's Morton index, Owen-scrambled in base 4 with a hash and a table of 24 orders: the same skeleton, used to give each pixel an offset into a sample sequence, not as a threshold.
  3. A. G. M. Ahmed, P. Wonka, "Screen-Space Blue-Noise Diffusion of Monte Carlo Sampling Error via Hierarchical Ordering of Pixels," ACM Transactions on Graphics 39(6), 2020. The published source of that idea: a hierarchically scrambled Morton order of pixels, so that the evenness of a sequence's blocks becomes blue noise on screen.
  4. K. Deschuytere (Agfa-Gevaert), US patent 5,602,971, "Multilevel halftoning using a randomised Bayer matrix," 1997. A random order drawn for every 2×2 submatrix of a Bayer matrix, on a stored tile.

Dither masks

  1. B. E. Bayer, "An Optimum Method for Two-Level Rendition of Continuous-Tone Pictures," IEEE International Conference on Communications, 1973. The ordered dither matrix: section 02 with every order fixed.
  2. T. Mitsa, K. J. Parker, "Digital Halftoning Technique Using a Blue-Noise Mask," Journal of the Optical Society of America A 9(11), 1992, 1920–1929.
  3. R. A. Ulichney, "The Void-and-Cluster Method for Dither Array Generation," Proc. SPIE 1913, 1993, 332–343. The standard against which the grain of section 03 should be judged.
  4. A. Wolfe, N. Morrical, T. Akenine-Möller, R. Ramamoorthi, "Spatiotemporal Blue Noise Masks," Eurographics Symposium on Rendering 2022, 117–126; on arXiv as "Scalar Spatiotemporal Blue Noise Masks," arXiv:2112.09629 (masks and code). Void and cluster with an energy shared only within a frame and along a pixel's timeline, stored as textures that tile in space and time. The closest in purpose to sections 04 and 05, by a different route, and a primary experimental comparison in section 11. It also explains why a plain 3D blue noise mask fails, which is the failure of the 2×2×2 blocks in section 04 seen in the spectrum.
  5. W. Donnelly, A. Wolfe, J. Bütepage, J. Valdés, "FAST: Filter-Adapted Spatio-Temporal Sampling for Real-Time Rendering," I3D 2024. Optimised spatiotemporal masks tuned to the filter that will average them.
  6. I. Georgiev, M. Fajardo, "Blue-Noise Dithered Sampling," ACM SIGGRAPH 2016 Talks.
  7. E. Heitz, L. Belcour, "Distributing Monte Carlo Errors as a Blue Noise in Screen Space by Permuting Pixel Seeds Between Frames," Computer Graphics Forum 38(4), 2019, 149–158.

Nets, scrambling, Latin squares

  1. H. Niederreiter, Random Number Generation and Quasi-Monte Carlo Methods, SIAM, 1992. The definition of a (t, m, s)-net: every box holding its share exactly is the property of sections 02 and 04.
  2. H. Faure, "Discrépance de suites associées à un système de numération (en dimension s)," Acta Arithmetica 41, 1982, 337–351. Powers of the Pascal matrix modulo a prime base give nets and sequences in as many coordinates as the base.
  3. H. Niederreiter, "Point Sets and Sequences with Small Discrepancy," Monatshefte für Mathematik 104, 1987, 273–337. Nets and sequences in general, and Faure's construction over a finite field for bases that are powers of a prime: the form used in section 04, over the field of four elements.
  4. A. B. Owen, "Randomly Permuted (t,m,s)-Nets and (t,s)-Sequences," Lecture Notes in Statistics 106, Springer, 1995, 299–317. Nested scrambling: an order that depends on the digits already decided.
  5. A. G. M. Ahmed, P. Wonka, "Optimizing Dyadic Nets," ACM Transactions on Graphics 40(4), 2021. Nets chosen for their spectra.
  6. M. D. McKay, R. J. Beckman, W. J. Conover, "A Comparison of Three Methods for Selecting Values of Input Variables in the Analysis of Output from a Computer Code," Technometrics 21(2), 1979, 239–245. Latin hypercube sampling: once per row and once per column, as a sampling design.
  7. B. Tang, "Orthogonal Array-Based Latin Hypercubes," Journal of the American Statistical Association 88(424), 1993, 1392–1397.
  8. K. Chiu, P. Shirley, C. Wang, "Multi-Jittered Sampling," Graphics Gems IV, 1994, 370–374; A. Kensler, "Correlated Multi-Jittered Sampling," Pixar Technical Memo 13-01, 2013. Jitter and Latin rows and columns together; the memo computes its permutations with a hash and stores nothing.
  9. P. Christensen, A. Kensler, C. Kilpatrick, "Progressive Multi-Jittered Sample Sequences," Computer Graphics Forum 37(4), 2018, 21–33.

Dither functions in use, and displays

  1. J. Jimenez, "Next Generation Post Processing in Call of Duty: Advanced Warfare," SIGGRAPH 2014 course Advances in Real-Time Rendering in Games. Interleaved gradient noise, the stateless per-pixel dither most games use.
  2. T. Ishii (S3 Graphics), US patent 6,362,834, "Flat-panel display controller with improved dithering and frame rate control," 2002. How display controllers combine a small dither matrix with a pattern over frames.

Where this came from

  1. Jan Boon (Kaetemi), "Stochastic Kernel-Switching Error Diffusion," blog.kaetemi.be, 2026. The population tree in the plane, and the threshold sweep made from the map used in section 03.
  2. Jan Boon (Kaetemi), "From Stateful to Stateless Blue Noise" and "Blue Noise Token Sampling," polyverse.dev, 2026.

Further comparisons: scrambling, interval inversion and temporal balance

  1. A. G. M. Ahmed, M. Pharr, P. Wonka, "ART-Owen Scrambling," ACM Transactions on Graphics 42(6), 2023, doi:10.1145/3618307. Grammar-based scrambling, arbitrary sample access and inversion; see §§4 and 6.2.
  2. A. G. M. Ahmed, "Q-ART Owen Scrambling," EG UK Computer Graphics & Visual Computing, 2025, doi:10.2312/cgvc.20251215. The two-page conference paper was read in full. This is not the expanded 2026 journal version, which remains to be reviewed.
  3. L. Grünschloß, M. Raab, A. Keller, "Enumerating Quasi-Monte Carlo Point Sequences in Elementary Intervals," Monte Carlo and Quasi-Monte Carlo Methods 2010, published 2012, pp. 399–408. Read in full; especially §4, equations (6)–(7), §4.1 on Faure constructions, and §5 on rendering.
  4. A. G. M. Ahmed, M. Pharr, V. Ostromoukhov, H. Huang, "NILE: Nested Interleaving of Low-Dimensional Elements," ACM Transactions on Graphics, July 2026, doi:10.1145/3811283 (paper). Read in full. The section above records the relevant overlaps; a complete equivalence comparison with the matrix construction here remains open.
  5. US patent 9,583,072 B2, "Spatial dithering for a display panel," 2017. The description's repeated seed and frame-counter construction is relevant to per-pixel temporal balance. Cited for its technical disclosure.
  6. US patent 8,928,688 B2, "Method for dithering in display panel and associated apparatus," 2015. The described pattern construction couples dithering with subpixel polarity. Cited for its technical disclosure, not as an equivalence finding for the Latin squares here.
  7. Christopher Wellons, hash-prospector (public domain): the two-round XOR/multiply mixer named lowbias32 in its README. The constants in the listings are the ones TheIronBorn posted in issue 19 on 2022-05-07 (bias 0.107 by the project's measure, against 0.174 for the README's); Boost's hash_mix_impl<32> (1.85.0) uses the same. An existing helper, not a new part of this construction.