SpatiotemporalBlueNoise

class SpatiotemporalBlueNoise

Spatiotemporal blue noise after Wolfe, Morrical, Akenine-Moller and Ramamoorthi, “Spatiotemporal Blue Noise Masks” (EGSR 2022): a stack of depth tileable size x size threshold masks, one a frame, such that every slice is blue noise over the screen and every pixel’s sequence through the slices is blue noise over time. A renderer that draws a fresh slice each frame gets noise that a spatial filter and a temporal accumulation (TAA) both remove well, where white noise or one 2D mask animated by a golden- ratio offset leaves low-frequency crawl.

The masks are ranked by Ulichney’s void-and-cluster with the paper’s separable energy: a Gaussian over the toroidal distance within a slice (sigma_space) plus a Gaussian over the toroidal distance between slices of the same pixel (sigma_time). Tightest clusters and largest voids are found with tournament trees, so the ranking costs O(N log N) for N = size^2 x depth (64 x 64 x 32 in about half a second).

The result is deterministic: one seed gives the same ranks on any machine with IEEE doubles (integer random numbers, ties broken by the lower index, energies summed in a fixed order with floating-point contraction off, kernels from an in-house exp(), not the C library’s). A stack holds at most 2^26 texels: a larger one logs an error and generate() does nothing.

Inheritance diagram

digraph inheritance2386e02944 { bgcolor=transparent; rankdir=BT; size="8.0, 12.0"; "DTOOL_SUPER_BASE" [fillcolor=white,fontname="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans",fontsize=11,height=0.25,shape=box,style=""]; "SpatiotemporalBlueNoise" [URL="acron.sampling.SpatiotemporalBlueNoise.html#acron.sampling.SpatiotemporalBlueNoise",fillcolor=white,fontname="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans",fontsize=11,height=0.25,shape=box,style="",target="_top",tooltip="/**"]; "DTOOL_SUPER_BASE" -> "SpatiotemporalBlueNoise" [arrowsize=0.75,style=""]; }
SpatiotemporalBlueNoise.__init__()

A stack of depth masks of size x size texels. Nothing is computed until generate(). sigma_space and sigma_time are the energy kernels’ standard deviations (Wolfe et al. use 1.9 for both).

SpatiotemporalBlueNoise.generate()

Ranks every texel. Phase 0 relaxes a random 10% pattern until its tightest cluster is its largest void; phase 1 removes the tightest clusters from it (ranks below its count, descending); phase 2 adds the largest voids (ranks from its count to the end). Past half the texels the largest void of the ones is the tightest cluster of the zeros (the kernels sum to the same total at every texel of the torus), so phase 2 also stands in for Ulichney’s phase 3.

SpatiotemporalBlueNoise.get_bytes()

Every texel as an 8-bit threshold (rank x 256 / count), x fastest, then y, then the slice: the RAM image of a size x size x depth single-channel texture array. Each of the 256 levels appears equally often when the count is a multiple of 256.

SpatiotemporalBlueNoise.get_depth()

The number of slices (frames before the sequence repeats).

SpatiotemporalBlueNoise.get_rank()

The rank (0 .. size^2 depth - 1) of a texel; coordinates wrap.

SpatiotemporalBlueNoise.get_seed()

The seed of the initial random pattern.

SpatiotemporalBlueNoise.get_sigma_space()

The spatial energy kernel’s standard deviation, in texels.

SpatiotemporalBlueNoise.get_sigma_time()

The temporal energy kernel’s standard deviation, in slices.

SpatiotemporalBlueNoise.get_size()

The side of each slice, in texels.

SpatiotemporalBlueNoise.get_value()

A texel’s threshold in (0, 1): (rank + 0.5) / count.

static SpatiotemporalBlueNoise.halton()

The radical inverse of index in the given base: element index of the Halton sequence along that axis (index 0 gives 0; TAA jitter starts at 1).

SpatiotemporalBlueNoise.is_generated()

True once generate() has ranked every texel.