This is unidirectional path tracing with uniform random variables everywhere, 8 bounces, RR, MIS, NEE, Lambert BRDF.Left side replaces uniform random variables with the tile. For subsequent pixels I shift the tile randomly over x and y, instead of adding an offset to each value.

This is from one of my private projects where i included the masks some months ago, using 3 dimensions to compute SSAO, left with 64x64 tiled blue noise, right with 64x64 tiled white noise (but both using the same QMC set for the samples in each pixel then). For such a case it can make a really nice difference.

Untitled.png (581.97 KiB) Viewed 3018 times

Better you leave here with your head still full of kitty cats and puppy dogs.

I was thinking of giving this technique a spin, and I had a couple of random thoughts.

1. The idea is basically to add the pixel-dependent blue-noise shift to a pixel-independent random number, and subtract 1 if necessary to bring things in the range of [0, 1), right? But during tile generation, the algorithm would think of (for one dimension in both domain and range) a set of values like [0.0, 0.99, 0.0, 0.99, 0.0] to be "good", but [0.0, 0.01, 0.0, 0.01, 0.0] to be "bad", even though they have very similar effects on the result? Maybe some other definition of distance (e.g., sin(2 * pi * (ps - qs)) * 0.5 + 0.5 for one dimension) is more appropriate?

2. Generating high-dimensional blue noise tiles is hard, but can you get a good-enough effect by just using multiple independent 1- and/or 2-dimensional tiles, or even the same tile at a different sufficiently-large offset per dimension?

The idea ideas not adding a deterministic (blue-noise) number to a random number. In fact, the idea is to get rid of the randomness altogether - you use the deterministic number to post your pattern, instead of using a random number.

Regarding the (0, 0.99) vs (0, 0.01) issue, the answer is that it depends. If your 1D sampling domain is "wrapped", i.e. 0.99 and 0.01 are next to each other, then yes, you should ideally take this into account in the distance function when you construct the dither mask. If the domain is not wrapped, then doing so will be suboptimal, that is, you won't get the best blue noise distribution of the error in your image. So you may want to have two dither masks - one with wrapped values and one with non-wrapped.

In 2D you have the same thing, but with some combinations. A torus light source would benefit from a mask with values wrapped along both dimensions. For a disk light or for hemisphere sampling you would use a mask wrapped along one dimension only. For a rectangular light the optimal choice would be a non-wrapped mask.

For higher dimensions using independent (or independently screen-space shifted) masks use possible, but if there's a lot of variance along all dimensions, then you will get some white noise that might destroy/mask the blue noise from the dithering. So it's not optimal, but it's very practical. Generating higher-dimensional blue noise masks is hard, because achieving high blue noise quality in high dimensions is hard in general.

ingenious wrote:The idea ideas not adding a deterministic (blue-noise) number to a random number. In fact, the idea is to get rid of the randomness altogether - you use the deterministic number to post your pattern, instead of using a random number.

Right, I was using "random" loosely. My understanding of this algorithm is that a Monte Carlo renderer where samples are taken per pixel might have a random number generator implemented like

And the halton() function might stand in for the "pixel-indendent random number".

Regarding the (0, 0.99) vs (0, 0.01) issue, the answer is that it depends. If your 1D sampling domain is "wrapped", i.e. 0.99 and 0.01 are next to each other, then yes, you should ideally take this into account in the distance function when you construct the dither mask.

Isn't the wrapping fundamental to the algorithm? If halton() returned 0.5 in the above code, then blue noise values of 0.0 and 0.99 would produce final results of 0.5 and 0.49, respectively.

Just to clarify, here the implementation (as described in the paper) computes the blue-noise mask using a distance over wrapped boundaries. If this is not required then it's a rather simple change to alter this behavior. On another note, I've recently added some progress feedback in the terminal as per jbikker's suggestion.