// Probabilistic quadtree filter
// https://ciphrd.com/2020/04/02/building-a-quadtree-filter-in-glsl-using-a-probabilistic-approach/
// the number of divisions at the start
//#define MIN_DIVISIONS 4.7
uniform float MIN_DIVISIONS;
// the number of possible quad divisions
//#define MAX_ITERATIONS 6
uniform int MAX_ITERATIONS;
// the number of samples picked after each quad division
#define SAMPLES_PER_ITERATION 30
#define F_SAMPLES_PER_ITERATION 30.
// useless, kept it for reference for a personal usage
// threshold min, max given the variance threshold (not mouse.x)
#define THRESHOLD_MIN 0.0001
#define THRESHOLD_MAX 0.01
// uniform float exampleUniform;
uniform vec2 iResolution;
uniform float varianceThreshold;
uniform sampler2D uNoiseMask; // Add this for the noise mask texture
// taken from http://glslsandbox.com/e#41197.0
float n = sin(dot(p, vec2(41, 289)));
return fract(vec2(262144, 32768)*n);
// Computes the color variation on a quad division of the space
// Basically, this method takes n random samples in a given quad, compute the average
// of each color component of the samples.
// Then, it computes the variance of the samples
// This is the way I thought for computing the color variation, there might be others,
// and there must be better ones
vec4 quadColorVariation(in vec2 center, in float size) {
// this array will store the grayscale of the samples
vec3 samplesBuffer[SAMPLES_PER_ITERATION];
// the average of the color components
// we sample the current space by picking pseudo random samples in it
for (int i = 0; i < SAMPLES_PER_ITERATION; i++) {
// pick a random 2d point using the center of the active quad as input
// this ensures that for every point belonging to the active quad, we pick the same samples
vec2 r = hash22(center.xy + vec2(fi, 0.0)) - 0.5;
vec3 sp = texture(sTD2DInputs[0], center + r * size).rgb;
avg /= F_SAMPLES_PER_ITERATION;
// estimate the color variation on the active quad by computing the variance
for (int i = 0; i < SAMPLES_PER_ITERATION; i++) {
var += pow(samplesBuffer[i], vec3(2.0));
var /= F_SAMPLES_PER_ITERATION;
var -= pow(avg, vec3(2.0));
return vec4(avg, (var.x + var.y + var.z) / 3.0);
// Normalized pixel coordinates (from 0 to 1)
float threshold = mix(THRESHOLD_MIN, THRESHOLD_MAX, varianceThreshold / iResolution.x);
// number of space divisions
float divs = MIN_DIVISIONS;
// the center of the active quad
vec2 quadCenter = (floor(uv * divs) + 0.5) / divs;
float quadSize = 1.0 / divs; // the size of each quad
// we store average and variance here
vec4 quadInfos = vec4(0);
for (int i = 0; i < MAX_ITERATIONS; i++) {
// Sample the noise mask at the current quad center
float noiseValue = texture(uNoiseMask, quadCenter).r;
// Adjust the subdivision threshold based on the noise mask value
float adjustedThreshold = threshold * mix(0.5, 1.5, noiseValue);
quadInfos = quadColorVariation(quadCenter, quadSize);
// Check against the adjusted threshold
if (quadInfos.w < adjustedThreshold) break;
// otherwise, divide the space again
quadCenter = (floor(uv * divs) + 0.5) / divs;
// Use the average color calculated from quadColorVariation
fragColor = TDOutputSwizzle(color);