
float CalculateSampleWeight(in vec4 centerData, in vec4 sampleData, in float centerDepth, 
                            in float sampleDepth, in vec3 centerNormal, in vec3 sampleNormal,
                            in float roughness, in float NdotV, in float coordOffset) {
    float smoothness = 1.0 - sqrt(roughness);
    
    float normalWeight = pow(max(dot(centerNormal, sampleNormal), 1e-6), 100.0 / max(roughness, 1e-5));
    
    float depthDiff = abs(sampleDepth - centerDepth);
    float depthWeight = fastExp(-depthDiff * 3.0 * NdotV * inversesqrt(coordOffset));
    
    float luminanceDiff = abs(GetLuminance(sampleData.rgb) - GetLuminance(centerData.rgb));
    float colorWeight = fastExp(-luminanceDiff * 2.0);
    
    float confidenceWeight = sampleData.a * 2.0 + 0.1;
    
    return normalWeight * depthWeight * colorWeight * confidenceWeight;
}

vec4 ReflectionFilter(in ivec2 texel, in vec4 reflectionData, in float roughness, 
                      in vec3 normal, in vec3 viewDir, in float size, in vec2 dither) {
    float smoothness = 1.0 - sqrt(roughness);
    float linearDepth = GetDepthLinear(texel);
    float NdotV = saturate(dot(-viewDir, normal));
    
    float coordOffset = 6.0 * size * min(roughness * 15.0, 1.0) * oneMinus(fastExp(-sqrt(reflectionData.a) * 40.0));
    coordOffset *= reflectionData.w * 0.6 + 0.4;
    
    if (isEyeInWater == 1) {
        coordOffset *= 0.6;
    }
    
    float sharpenWeight = reflectionData.w * 0.4 + 0.1;
    
    vec3 safeColor = max(reflectionData.rgb, 1e-6);
    vec3 transformedColor = pow(dotSelf(safeColor), 0.5 * sharpenWeight) * normalize(safeColor);
    
    vec4 filteredData = vec4(transformedColor, reflectionData.a);
    float sumWeight = 1.0;
    
    for (uint i = 0u; i < 8u; ++i) {
        vec2 offset = offset3x3N[i] + dither * 0.5;
        ivec2 sampleTexel = clamp(texel + ivec2(offset * coordOffset), ivec2(0), ivec2(screenSize - 1));
        
        vec4 sampleData = texelFetch(colortex2, sampleTexel, 0);
        float sampleLinearDepth = GetDepthLinear(sampleTexel);
        vec3 sampleNormal = GetNormals(sampleTexel);
        
        float weight = CalculateSampleWeight(reflectionData, sampleData, linearDepth, 
                                              sampleLinearDepth, normal, sampleNormal,
                                              roughness, NdotV, coordOffset);
        
        vec3 safeSampleColor = max(sampleData.rgb, 1e-6);
        vec3 transformedSample = pow(dotSelf(safeSampleColor), 0.5 * sharpenWeight) * normalize(safeSampleColor);
        
        filteredData += vec4(transformedSample, sampleData.a) * weight;
        sumWeight += weight;
    }
    
    if (sumWeight < 1e-3) {
        return vec4(safeColor, reflectionData.a);
    }
    
    filteredData /= sumWeight;
    
    filteredData.rgb = max(filteredData.rgb, 1e-6);
    filteredData.rgb = pow(dotSelf(filteredData.rgb), 0.5 / sharpenWeight) * normalize(filteredData.rgb);
    
    return filteredData;
}

vec4 UnderwaterReflectionFilter(in ivec2 texel, in vec4 reflectionData, in float roughness, 
                                 in vec3 normal, in vec3 viewDir, in float size, in vec2 dither) {
    float linearDepth = GetDepthLinear(texel);
    float NdotV = saturate(dot(-viewDir, normal));
    
    float coordOffset = 3.0 * size * min(roughness * 10.0, 1.0);
    coordOffset *= reflectionData.w * 0.5 + 0.5;
    
    float sharpenWeight = 0.5;
    
    vec3 safeColor = max(reflectionData.rgb, 1e-6);
    vec3 transformedColor = pow(dotSelf(safeColor), 0.5 * sharpenWeight) * normalize(safeColor);
    
    vec4 filteredData = vec4(transformedColor, reflectionData.a);
    float sumWeight = 1.0;
    
    const ivec2 smallOffsets[4] = ivec2[](
        ivec2(1, 0), ivec2(-1, 0), ivec2(0, 1), ivec2(0, -1)
    );
    
    for (uint i = 0u; i < 4u; ++i) {
        ivec2 sampleTexel = clamp(texel + ivec2(vec2(smallOffsets[i]) * coordOffset), ivec2(0), ivec2(screenSize - 1));
        
        vec4 sampleData = texelFetch(colortex2, sampleTexel, 0);
        float sampleLinearDepth = GetDepthLinear(sampleTexel);
        vec3 sampleNormal = GetNormals(sampleTexel);
        
        float normalWeight = pow(max(dot(normal, sampleNormal), 0.0), 200.0);
        float depthWeight = fastExp(-abs(sampleLinearDepth - linearDepth) * 5.0);
        float weight = normalWeight * depthWeight * sampleData.a;
        
        vec3 safeSampleColor = max(sampleData.rgb, 1e-6);
        vec3 transformedSample = pow(dotSelf(safeSampleColor), 0.5 * sharpenWeight) * normalize(safeSampleColor);
        
        filteredData += vec4(transformedSample, sampleData.a) * weight;
        sumWeight += weight;
    }
    
    if (sumWeight < 1e-3) {
        return vec4(safeColor, reflectionData.a);
    }
    
    filteredData /= sumWeight;
    filteredData.rgb = max(filteredData.rgb, 1e-6);
    filteredData.rgb = pow(dotSelf(filteredData.rgb), 0.5 / sharpenWeight) * normalize(filteredData.rgb);
    
    return filteredData;
}