float AmbientOcclusion(float dither, float depth) {
    float ao = 0.0;

    depth = ld(depth);

    float currentStep = 0.2 * dither + 0.2;

    float fovScale = gbufferProjection[1][1] / 1.37;
    float distScale = max((far - near) * depth + near, 5.0);
    vec2 scale = 0.35 * vec2(1.0 / aspectRatio, 1.0) * fovScale / distScale;
    float mult = (0.5 / 0.35) * (far - near);

    // Calculate values that are used multiple times
    float step0 = currentStep + 0.0;
    float step2 = currentStep + 0.2;
    float step4 = currentStep + 0.4;
    float step6 = currentStep + 0.6;

    // Use the calculated values in the texoffsets0 and texoffsets1 arrays
    ivec2 texoffsets0[4] = ivec2[](ivec2((((OffsetDist(step0) * scale) * viewResolution) + 0.0)), ivec2((((OffsetDist(step2) * scale) * viewResolution) + 0.2)), ivec2((((OffsetDist(step4) * scale) * viewResolution) + 0.4)), ivec2((((OffsetDist(step6) * scale) * viewResolution) + 0.6)));
    ivec2 texoffsets1[4] = ivec2[](ivec2(-(((OffsetDist(step0) * scale) * viewResolution) + 0.0)), ivec2(-(((OffsetDist(step2) * scale) * viewResolution) + 0.2)), ivec2(-(((OffsetDist(step4) * scale) * viewResolution) + 0.4)), ivec2(-(((OffsetDist(step6) * scale) * viewResolution) + 0.6)));

    vec4 depthgather0 = ld4(textureGatherOffsets(depthtex0, texcoord, texoffsets0, 0));
    vec4 depthgather1 = ld4(textureGatherOffsets(depthtex0, texcoord, texoffsets1, 0));
    vec4 cstep = vec4(currentStep, currentStep + 0.2, currentStep + 0.4, currentStep + 0.6);
    vec4 sample00 = (depth - (depthgather0)) * mult / cstep;
    vec4 sample01 = (depth - (depthgather1)) * mult / cstep;
    vec4 angle0 = clamp(0.5 - sample00, 0.0, 1.0) + clamp(0.5 - sample00, 0.0, 1.0);
    vec4 dist1 = clamp(0.25 * sample01 - 1.0, 0.0, 1.0) + clamp(0.25 * sample01 - 1.0, 0.0, 1.0);
    vec4 ao2 = clamp(angle0 + dist1, 0.0, 1.0);
    ao = clamp(dot(ao2, vec4(1.0)) * 0.33, 0, 1);

    return ao;
}
float waterCaustics(vec3 wPos) {
    vec2 pos = (wPos.xz) * 4.0;
    vec2 movement = vec2(-0.02 * frameTimeCounter);
    float caustic = 0.0;
    float weightSum = 0.0;
    float radiance = 2.39996;
    mat2 rotationMatrix = mat2(vec2(cos(radiance), -sin(radiance)), vec2(sin(radiance), cos(radiance)));
    for (int i = 0; i < 5; i++) {
        vec2 displ = texture(noisetex, pos / 32.0 + movement).bb * 2.0 - 1.0;
        pos = rotationMatrix * pos;
        caustic += pow(0.5 + sin(dot((pos + vec2(1.74 * frameTimeCounter)) * exp2(0.8 * i) + displ * 3.0, vec2(0.5))) * 0.5, 6.0) *
            exp2(-0.8 * i) / 1.41;
        weightSum += exp2(-0.8 * i);
    }
    return caustic * weightSum;
}

#ifdef SSPT
ambientLight = ambientLight * 0.5 +
    rtGI(wNormal, normal, noise, fragpos, ambientLight, vec3(emissive * 3), SSS, albedo, lightmap, hand);
#endif

/*if (isEyeInWater == 1)
{
    float estimatedDepth = clamp((15.5 - lightmap.y * 16.0) / 15.5, 0.0, 1.0);
    estimatedDepth *= estimatedDepth * estimatedDepth * 32.0;

    float estimatedSunDepth = estimatedDepth / abs(WsunVec.y); // assuming water plane
    diffuseLight *= exp(-totEpsilon * estimatedSunDepth) * (0.91 - pow(1.0 - WsunVec.y, 5.0) * 0.86);

    float caustics = waterCaustics(mat3(gbufferModelViewInverse) * fragpos + gbufferModelViewInverse[3].xyz +
                                   cameraPosition);
    diffuseLight *= mix(caustics * 0.5 + 0.5, 1.0, exp(-estimatedSunDepth / 3.0));
    diffuseLight = max(diffuseLight, 0);
}*/

vec3 toShadowSpace(vec3 p3)
{
    p3 = mat3(gbufferModelViewInverse) * p3 + gbufferModelViewInverse[3].xyz;
    p3 = mat3(shadowModelView) * p3 + shadowModelView[3].xyz;
    return p3;
}

vec3 toShadowSpaceProjected(vec3 p3)
{
    p3 = mat3(gbufferModelViewInverse) * p3 + gbufferModelViewInverse[3].xyz;
    p3 = mat3(shadowModelView) * p3 + shadowModelView[3].xyz;
    p3 = diagonal3(shadowProjection) * p3 + shadowProjection[3].xyz;

    return p3;
}
/*

float cloudVol2(in vec3 pos)
{
float modWT = (worldTime % 24000);

#define FOG_TOD_MULTIPLIER 1.0
#define FOG_RAIN_MULTIPLIER 0.1

float fogAmount = (max(FOG_RAIN_MULTIPLIER * 1 / 20 * (rainStrength * 0.7),
                   FOG_TOD_MULTIPLIER * 1 / 75 * clamp(modWT - 13000, 10, 1000) / 1000 *
                       (1.0 - clamp(modWT - 23000, 10, 1000) / 1000)));
float unifCov = exp2(-max(pos.y - SEA_LEVEL, 0.0) / 50.0);
float cloud = unifCov * 60.0 * fogAmount;
return cloud;
}

#define diagonal3(m) vec3((m)[0].x, (m)[1].y, m[2].z)

vec3 toShadowSpaceProjected(vec3 p3)
{
p3 = mat3(gbufferModelViewInverse) * p3 + gbufferModelViewInverse[3].xyz;
p3 = mat3(shadowModelView) * p3 + shadowModelView[3].xyz;
p3 = diagonal3(shadowProjection) * p3 + shadowProjection[3].xyz;

return p3;
}

mat2x3 getVolumetricRays2(vec3 fragpos, vec3 ambientUp, vec3 scatterCoef, vec3 totEpsilon, vec3 sunColor2)
{
int VL_SAMPLES = 6;
float dither = blueNoise();
vec4 lightCol2 = vec4(sunColor2.rgb, float(sunElevation > 1e-5) * 2.0 - 1.0);

// project pixel position into projected shadowmap space
vec3 wpos = mat3(gbufferModelViewInverse) * fragpos + gbufferModelViewInverse[3].xyz;
vec3 fragposition = mat3(shadowModelView) * wpos + shadowModelView[3].xyz;
fragposition = diagonal3(shadowProjection) * fragposition + shadowProjection[3].xyz;

// project view origin into projected shadowmap space
vec3 start = toShadowSpaceProjected(vec3(0.));

// rayvector into projected shadow map space
// we can use a projected vector because its orthographic projection
// however we still have to send it to curved shadow map space every step
vec3 dV = (fragposition - start);
vec3 dVWorld = (wpos - gbufferModelViewInverse[3].xyz);

float maxLength = min(length(dVWorld), far) / length(dVWorld);
dV *= maxLength;
dVWorld *= maxLength;

// apply dither
vec3 progress = start.xyz;
vec3 progressW = gbufferModelViewInverse[3].xyz + cameraPosition;
vec3 vL = vec3(0.);

float SdotV = clamp(dot(sunPosition, normalize(fragpos)) * 0.01, 0.0, 1.0) * lightCol2.a;
float dL = length(dVWorld);
// Mie phase + somewhat simulates multiple scattering (Horizon zero down cloud approx)
float mie = max(phaseg(SdotV, 0.4), 0.076);
float rayL = phaseRayleigh(SdotV);
//	wpos.y = clamp(wpos.y,0.0,1.0);

vec3 ambientCoefs = dVWorld / dot(abs(dVWorld), vec3(1.));

vec3 skyCol0 = ambientUp * eyeBrightnessSmooth.y / vec3(240.);
vec3 sunColor = (lightCol2.rgb * 5) * (eyeBrightnessSmooth.y / 240.0);

vec3 rC = mix(vec3(toLinear(skyColor * 2) * 1e-5), vec3(0.0000058, 0.0000135, 0.0000331) * 0.3, 0.0);

float mu = 1.0;
float muS = 1.0 * mu;
vec3 absorbance = vec3(1.0);
float expFactor = 11.0;
vec3 WsunVec = mat3(gbufferModelViewInverse) * sunVec;
for (int i = 0; i < VL_SAMPLES; i++)
{
float d = (pow(expFactor, float(i + dither) / float(VL_SAMPLES)) / expFactor - 1.0 / expFactor) /
          (1 - 1.0 / expFactor);
float dd = pow(expFactor, float(i + dither) / float(VL_SAMPLES)) * log(expFactor) / float(VL_SAMPLES) /
           (expFactor - 1.0);
progress = start.xyz + d * dV;
progressW = gbufferModelViewInverse[3].xyz + cameraPosition + d * dVWorld;

vec3 pos = vec3(progress.xy * calcDistort(progress.xy), progress.z);
float densityVol = cloudVol2(progressW);
float sh = 1.0;
if (abs(pos.x) < 1.0 - 0.5 / 2048. && abs(pos.y) < 1.0 - 0.5 / 2048)
{
    pos = pos * vec3(0.5, 0.5, 0.5 / 6.001) + 0.5;
    sh = shadow2D(shadow, pos).x;
}
// Water droplets(fog)
float density = densityVol * 4.0 * 5000.0;
// Just air
vec2 airCoef = exp2(-max(progressW.y - SEA_LEVEL, 0.0) / vec2(48000, 8400)) * 6.0;

// Pbr for air, yolo mix between mie and rayleigh for water droplets
vec3 rL = rC * airCoef.x;
vec3 m = (airCoef.y + density) * vec3(0.0000030);
vec3 vL0 = sunColor * sh * (rayL * rL + m * mie) + skyCol0 * (rL + m);

vL += (vL0 - vL0 * exp(-(rL + m) * dd * dL)) / ((rL + m) + 0.00000001) * absorbance;
absorbance *= clamp(exp(-(rL + m) * dd * dL), 0.0, 1.0);
}
return mat2x3(vL, absorbance);
}*/

/*1000: (255, 56, 0),
1100: (255, 71, 0),
1200: (255, 83, 0),
1300: (255, 93, 0),
1400: (255, 101, 0),
1500: (255, 109, 0),
1600: (255, 115, 0),
1700: (255, 121, 0),
1800: (255, 126, 0),
1900: (255, 131, 0),
2000: (255, 138, 18),
2100: (255, 142, 33),
2200: (255, 147, 44),
2300: (255, 152, 54),
2400: (255, 157, 63),
2500: (255, 161, 72),
2600: (255, 165, 79),
2700: (255, 169, 87),
2800: (255, 173, 94),
2900: (255, 177, 101),
3000: (255, 180, 107),
3100: (255, 184, 114),
3200: (255, 187, 120),
3300: (255, 190, 126),
3400: (255, 193, 132),
3500: (255, 196, 137),
3600: (255, 199, 143),
3700: (255, 201, 148),
3800: (255, 204, 153),
3900: (255, 206, 159),
4000: (255, 209, 163),
4100: (255, 211, 168),
4200: (255, 213, 173),
4300: (255, 215, 177),
4400: (255, 217, 182),
4500: (255, 219, 186),
4600: (255, 221, 190),
4700: (255, 223, 194),
4800: (255, 225, 198),
4900: (255, 227, 202),
5000: (255, 228, 206),
5100: (255, 230, 210),
5200: (255, 232, 213),
5300: (255, 233, 217),
5400: (255, 235, 220),
5500: (255, 236, 224),
5600: (255, 238, 227),
5700: (255, 239, 230),
5800: (255, 240, 233),
5900: (255, 242, 236),
6000: (255, 243, 239),
6100: (255, 244, 242),
6200: (255, 245, 245),
6300: (255, 246, 247),
6400: (255, 248, 251),
6500: (255, 249, 253),
6600: (254, 249, 255),
6700: (252, 247, 255),
6800: (249, 246, 255),
6900: (247, 245, 255),
7000: (245, 243, 255),
7100: (243, 242, 255),
7200: (240, 241, 255),
7300: (239, 240, 255),
7400: (237, 239, 255),
7500: (235, 238, 255),
7600: (233, 237, 255),
7700: (231, 236, 255),
7800: (230, 235, 255),
7900: (228, 234, 255),
8000: (227, 233, 255),
8100: (225, 232, 255),
8200: (224, 231, 255),
8300: (222, 230, 255),
8400: (221, 230, 255),
8500: (220, 229, 255),
8600: (218, 229, 255),
8700: (217, 227, 255),
8800: (216, 227, 255),
8900: (215, 226, 255),
9000: (214, 225, 255),
9100: (212, 225, 255),
9200: (211, 224, 255),
9300: (210, 223, 255),
9400: (209, 223, 255),
9500: (208, 222, 255),
9600: (207, 221, 255),
9700: (207, 221, 255),
9800: (206, 220, 255),
9900: (205, 220, 255),
10000: (207, 218, 255),
10100: (207, 218, 255),
10200: (206, 217, 255),
10300: (205, 217, 255),
10400: (204, 216, 255),
10500: (204, 216, 255),
10600: (203, 215, 255),
10700: (202, 215, 255),
10800: (202, 214, 255),
10900: (201, 214, 255),
11000: (200, 213, 255),
11100: (200, 213, 255),
11200: (199, 212, 255),
11300: (198, 212, 255),
11400: (198, 212, 255),
11500: (197, 211, 255),
11600: (197, 211, 255),
11700: (197, 210, 255),
11800: (196, 210, 255),
11900: (195, 210, 255),
12000: (195, 209, 255)}*/
#define SAMPLES 16 // HIGHER = NICER = SLOWER
#define DISTRIBUTION_BIAS 0.6 // between 0. and 1.
#define PIXEL_MULTIPLIER 1.5 // between 1. and 3. (keep low)
#define INVERSE_HUE_TOLERANCE 20.0 // (2. - 30.)

#define GOLDEN_ANGLE 2.3999632 // 3PI-sqrt(5)PI

#define pow(a, b) pow(max(a, 0.), b) // @morimea

mat2 sample2D = mat2(cos(GOLDEN_ANGLE), sin(GOLDEN_ANGLE), - sin(GOLDEN_ANGLE), cos(GOLDEN_ANGLE));

vec3 sirBirdDenoise(sampler2D imageTexture, in vec2 uv, in vec2 imageResolution) {

vec3 denoisedColor = vec3(0.);

const float sampleRadius = sqrt(float(SAMPLES));
const float sampleTrueRadius = 0.5 / (sampleRadius * sampleRadius);
vec2 samplePixel = vec2(1.0 / imageResolution.x, 1.0 / imageResolution.y);
vec3 sampleCenter = texture(imageTexture, uv).rgb;
vec3 sampleCenterNorm = normalize(sampleCenter);
float sampleCenterSat = length(sampleCenter);

float influenceSum = 0.0;
float brightnessSum = 0.0;

vec2 pixelRotated = vec2(0., 1.);

for (float x = 0.0;
x <= float(SAMPLES);
x ++) {

pixelRotated *= sample2D;

vec2 pixelOffset = PIXEL_MULTIPLIER * pixelRotated * sqrt(x) * 0.5;
float pixelInfluence = 1.0 - sampleTrueRadius * pow(dot(pixelOffset, pixelOffset), DISTRIBUTION_BIAS);
pixelOffset *= samplePixel;

vec3 thisDenoisedColor = texture(imageTexture, uv + pixelOffset).rgb;

pixelInfluence *= pixelInfluence * pixelInfluence;
        /*
            HUE + SATURATION FILTER
        */
pixelInfluence *= pow(0.5 + 0.5 * dot(sampleCenterNorm, normalize(thisDenoisedColor)), INVERSE_HUE_TOLERANCE) *
    pow(1.0 - abs(length(thisDenoisedColor) - length(sampleCenterSat)), 8.);

influenceSum += pixelInfluence;
denoisedColor += thisDenoisedColor * pixelInfluence;
}

return denoisedColor / influenceSum;
}

uniform float frameTimeCounter;
const float PI48 = 150.796447372 * WAVY_SPEED;
float pi2wt = PI48 * frameTimeCounter;
float waterH(vec3 posxz, float movement) {

float wave = 0.0;

float factor = 1.0;
float amplitude = 0.05;
float speed = 4.0;
float size = 1.0;

float px = posxz.x / 50.0 + 250.0;
float py = posxz.z / 50.0 + 250.0;

float fpx = abs(fract(px * 20.0) - 0.5) * 2.0;
float fpy = abs(fract(py * 20.0) - 0.5) * 2.0;

float d = length(vec2(fpx, fpy));

for (int i = 0;
i < 3;
i ++) {
wave -= d * factor * cos((1 / factor) * px * py * size + 1.0 * (movement) * speed);
factor /= 2;
}

factor = 1.0;
px = - posxz.x / 50.0 + 250.0;
py = - posxz.z / 150.0 - 250.0;

fpx = abs(fract(px * 20.0) - 0.5) * 2.0;
fpy = abs(fract(py * 20.0) - 0.5) * 2.0;

d = length(vec2(fpx, fpy));
float wave2 = 0.0;
for (int i = 0;
i < 3;
i ++) {
wave2 -= d * factor * cos((1 / factor) * px * py * size + 1.0 * (movement) * speed);
factor /= 2;
}

return (amplitude * wave2 + amplitude * wave) * 0.0005;
}
            //diffuseSun = OrenNayar(WsunVec, wNormal, normalize(p3), roughness);
/*
    vec3 dirLightMapCoordT = dFdx(fragpos.xyz) * dFdx(lmtexcoord2.z) + dFdy(fragpos.xyz) * dFdy(lmtexcoord2.z);
    vec3 dirLightMapCoordS = dFdx(fragpos.xyz) * dFdx(lmtexcoord2.w) + dFdy(fragpos.xyz) * dFdy(lmtexcoord2.w);

    float dirLightMapT = min(1.0, max(0.0, dot(normalize(dirLightMapCoordT), normal)) * lmtexcoord2.z +
    lmtexcoord2.z); float dirLightMapS = min(1.0, max(0.0, dot(normalize(dirLightMapCoordS), normal)) *
    lmtexcoord2.w
    + lmtexcoord2.w);

    lmtexcoord2.zw = mix(vec2(dirLightMapT*1.1, dirLightMapS) * lmtexcoord.zw, lmtexcoord.zw, 0.5);
*/
/*
vec2 delta = texelSize * 0.25;
    float h0 = clamp(luma(texture(texture, tcoord.xy).rgb) / luma(textureLod(texture, tcoord.xy, 100).rgb), 0, 1);
    float h1 = clamp(luma(texture(texture, tcoord.xy + vec2(delta.x, 0.0)).rgb) /
                         luma(textureLod(texture, tcoord.xy + vec2(delta.x, 0.0), 100).rgb),
                     0, 1);
    float h2 = clamp(luma(texture(texture, tcoord.xy + vec2(0.0, delta.y)).rgb) /
                         luma(textureLod(texture, tcoord.xy + vec2(0.0, delta.y), 100).rgb),
                     0, 1);

    vec2 dXY = clamp(vec2(h0 - h1, h0 - h2) / delta, -1, 1);
    vec2 nXY = clamp(sin(atan(dXY)), -1, 1);

    normalTex = pow(clamp(mix(normalize(vec3(nXY, 1.0)), normalTex - 0.5, 0.0), -1, 1) , vec3(2));

    normalTex += 0.5;
*/

#define DRAG_MULT 0.048

vec2 wavedx(vec2 position, vec2 direction, float speed, float frequency, float timeshift) {
    // calculate the wave using the dot product of the direction and position vectors
    // and the given frequency and timeshift parameters
    float x = dot(direction, position) * frequency + timeshift * speed;
    float wave = exp(sin(x) - 1.0);

    // calculate the derivative of the wave with respect to x
    float dx = wave * cos(x);

    // return the wave value and its derivative
    return vec2(wave, -dx);
}
float getwaves(vec3 position0, float fresnel) {
    // convert the 3D position to 2D
    vec2 position = position0.xz;

    // initialize variables
    float iter = 0.0;
    float phase = 0.25;
    float speed = 1.0;
    float weight = 2.0;
    float w = 0.0;
    float ws = 0.0;
    const float quality = 16;

    // loop through the waves
    for (int i = 0; i < quality; i++) {
        // calculate the sine and cosine of the iteration value
        float sinIter = sin(iter);
        float cosIter = cos(iter);

        // calculate the wave direction vector
        vec2 p = vec2(sinIter, cosIter);

        // calculate the wave and its derivative
        vec2 res = wavedx(position, p, speed, phase, frameTimeCounter);

        // update the position and wave values
        position += p * res.y * weight * DRAG_MULT;
        w += res.x * weight;
        iter += 12.0;
        ws += weight;
        weight = weight * 0.8;
        phase *= 1.18;
        speed *= 1.07;
    }

    // return the average wave value
    return (1 - (w / ws)) * 0.25;
}

void temporal(inout vec3 indirectCurrent, inout vec4 historyGData, inout vec4 indirectHistory, vec3 fragpos, vec3 normal2, vec2 texcoord, bool hand) {

        vec3 scenePos = viewMAD(gbufferModelViewInverse, fragpos);
        vec3 reprojection = reproject(scenePos, hand);

        bool offscreen = clamp(reprojection, 0, 1) != reprojection;
        vec3 cameraMovement = mat3(gbufferModelView) * (cameraPosition - previousCameraPosition);
        vec4 currentGData = vec4(normal2, 0);
        currentGData.rgb = currentGData.rgb;
        currentGData.a = length(fragpos);

        reprojection.xy *= RENDER_SCALE;
        historyGData = texture(colortex9, reprojection.xy);
        historyGData.rgb = historyGData.rgb;
        vec2 moment = moment(ivec2(floor(reprojection.xy * vec2(viewWidth, viewHeight))));
        gl_FragData[4].rg = moment;

        float distanceDelta = distance(historyGData.a * far, currentGData.a) - abs(cameraMovement.z);

        float depthRejection = (offscreen) ? 0.0 : exp(-max(distanceDelta - 0.2, 0.0) * 3.0);

        indirectHistory = texture(colortex9, reprojection.xy);
        indirectHistory.a = clamp(indirectHistory.a, 0, 1);

        float normalWeight = sqr(clamp(dot(historyGData.rgb, currentGData.rgb), 0, 1));

        float accumulationR0 = mix(0.1, 0.02, sqrt(indirectHistory.a));

        float varianceSpatial = clamp((max(0.0, moment.y - moment.x * moment.x) * 100), 0, 1);

        varianceSpatial = accumulationR0 + varianceSpatial;
        float accumulationWeight = depthRejection;
        accumulationWeight *= normalWeight;
        accumulationWeight += clamp(((distance(indirectHistory.rgb, indirectCurrent.rgb) / luma(indirectHistory.rgb))) *
                (abs(cameraMovement.r) + abs(cameraMovement.g) + abs(cameraMovement.b)), 0, 1);
        accumulationWeight = clamp(1.0 - accumulationWeight, 0.0, 1);
        accumulationWeight = max(accumulationWeight, varianceSpatial);

        vec3 albedoCurrent0 = texture(colortex9, texcoord).rgb;
        vec3 albedoCurrent1 = texture(colortex9, texcoord + vec2(texelSize.x, texelSize.y)).rgb;
        vec3 albedoCurrent2 = texture(colortex9, texcoord + vec2(texelSize.x, -texelSize.y)).rgb;
        vec3 albedoCurrent3 = texture(colortex9, texcoord + vec2(-texelSize.x, -texelSize.y)).rgb;
        vec3 albedoCurrent4 = texture(colortex9, texcoord + vec2(-texelSize.x, texelSize.y)).rgb;
        vec3 albedoCurrent5 = texture(colortex9, texcoord + vec2(0.0, texelSize.y)).rgb;
        vec3 albedoCurrent6 = texture(colortex9, texcoord + vec2(0.0, -texelSize.y)).rgb;
        vec3 albedoCurrent7 = texture(colortex9, texcoord + vec2(-texelSize.x, 0.0)).rgb;
        vec3 albedoCurrent8 = texture(colortex9, texcoord + vec2(texelSize.x, 0.0)).rgb;

        vec3 cMax = max(max(max(albedoCurrent0, albedoCurrent1), albedoCurrent2), max(albedoCurrent3, max(albedoCurrent4, max(albedoCurrent5, max(albedoCurrent6, max(albedoCurrent7, albedoCurrent8))))));
        vec3 cMin = min(min(min(albedoCurrent0, albedoCurrent1), albedoCurrent2), min(albedoCurrent3, min(albedoCurrent4, min(albedoCurrent5, min(albedoCurrent6, min(albedoCurrent7, albedoCurrent8))))));

        vec3 albedoPrev = max(FastCatmulRom(colortex9, reprojection.xy, vec4(texelSize, 1.0 / texelSize), 0.75).xyz, 0.0);
        vec3 finalcAcc = clamp(albedoPrev, cMin, cMax);

        float isclamped = (clamp(clamp(((distance(albedoPrev, finalcAcc) / luma(albedoPrev))), 0, 1), 0.0, 1)) * 0.5;

        accumulationWeight = clamp((accumulationWeight) + isclamped, 0.0, 1);

        if (!offscreen) {

                indirectHistory.rgb = mix(indirectHistory.rgb, indirectCurrent, accumulationWeight);

                indirectHistory.a = mix(0.0, clamp(indirectHistory.a + (1 / (30)), 0, 1), float(distanceDelta < 0.2));
                if (hand)
                        indirectHistory.a = 1;
        } else {
                indirectHistory = vec4(mix(indirectHistory.rgb, indirectCurrent, 0.9), 0.0);
        }

        indirectCurrent = indirectHistory.rgb;
        historyGData.rgb = currentGData.rgb;
        historyGData.a = currentGData.a / far;

        indirectCurrent = indirectCurrent;
        historyGData = historyGData;
        indirectHistory = indirectHistory;
}
int BOX_SIZE = 3;

vec4 tx(sampler2D tex, vec2 uv, vec2 l) {
    return texture(tex, uv + l / viewResolution.xy);
}
vec4 dilated(in sampler2D tex, in vec2 uv) {
    // This uses box as a structuring element
    // Triangle may be constructed by starting with j=i
    vec4 maxColor = vec4(0.0);
    int B_SIZE = BOX_SIZE / 2;
    for (int i = -B_SIZE; i < B_SIZE; i++) {
        for (int j = -B_SIZE; j < B_SIZE; j++) {
            maxColor = max(tx(tex, uv, vec2(i, j)), maxColor);
        }
    }
    return maxColor;
}
vec3 checkerboard(vec3 a, vec3 b, vec2 coord) {
  // Compute the checkerboard pattern using the coordinates
    float check = mod(coord.x + coord.y, 2.0);

  // Return the appropriate color based on the checkerboard pattern
    if (check < 1.0) {
        return a;
    } else {
        return b;
    }
}


float gaussianWeight(ivec2 offset, vec2 coord) {
  // Compute the distance from the current pixel to the neighboring pixel
    float distance = length(vec2(offset));

  // Compute the Gaussian weight using the distance and a predefined standard deviation
    float sigma = 1.0;
    return exp(-distance * distance / (2.0 * sigma * sigma)) / (sqrt(2.0 * 3.141592) * sigma);
}


vec3 reconstruct(vec3 a, sampler2D b, vec2 coord, bool reconstructA) {
  // Compute the checkerboard pattern using the coordinates
    float check = mod(coord.x + coord.y, 2.0);

  // Return the appropriate color based on the reconstruction flag and the checkerboard pattern
    if (reconstructA && check < 1.0) {
        return a;
    } else if (!reconstructA && check >= 1.0) {
        return a;
    } else {
    // If the reconstruction flag and the checkerboard pattern do not match,
    // fill in the missing color using the neighboring pixels with a Gaussian weight
        ivec2 s0 = ivec2(0.0, 1.0);
        ivec2 s1 = ivec2(1.0, 0.0);

        vec3 neighborA = texelFetch(b, ivec2(coord + s0), 0).rgb;
        vec3 neighborB = texelFetch(b, ivec2(coord - s0), 0).rgb;
        vec3 neighborC = texelFetch(b, ivec2(coord + s1), 0).rgb;
        vec3 neighborD = texelFetch(b, ivec2(coord - s1), 0).rgb;

        float wA = gaussianWeight(s0, coord);
        float wB = gaussianWeight(s0, coord);
        float wC = gaussianWeight(s1, coord);
        float wD = gaussianWeight(s1, coord);

        return (neighborA * wA + neighborB * wB + neighborC * wC + neighborD * wD) / (wA + wB + wC + wD);
    }
}
