I am building a 3d renderer in C++ using Vulkan.hpp. I am in the process of implementing SSAO into my rendering pipeline using the depth buffer as in input. So far I have managed to get something that looks like an SSAO output. However, it seems the output is still being impacted by the depth buffer. The occlusion at far distances effectively becomes black, while the occlusion close to the camera is muted. I have inverted the input of the depth image after linearization since I'm sure the buffer must be inverted. Without this adjustment the occlusion seems inverted so it seems like the correct approach. Do you have any idea what might be causing the depth weighting on the occlusion output? I have included the code for the SSAO fragment shader below for reference. Also as a note, the UBO inputs for the InputsBufferObject are as follows: kernelSize = 32, radius = 1.5, bias = 0.025. noiseScale = vec2(swapchain.width/ssaoNoiseSize, swapchain.height/ssaoNoiseSize)

#version 450

layout(location = 0) out vec4 FragColor;

layout(location = 0) in vec2 TexCoords;

layout(set = 0, binding = 0) uniform sampler2D gDepth;
layout(set = 0, binding = 1) uniform sampler2D gNormal;
layout(set = 0, binding = 2) uniform sampler2D texNoise;

layout(binding = 3) buffer SamplesBufferObject {
    vec3 samples[];
} samplesUBO;

layout(binding = 4) uniform InputsBufferObject {
    int kernelSize;
    float radius;
    float bias;
    vec2 noiseScale;
} inputsUBO;

layout(binding = 5) uniform ProjBufferObject {
    mat4 proj;
    mat4 projInv;
} projUBO;

// Function to get the view-space position from depth
vec3 getViewPos(vec2 uv, float depth, mat4 projInv) {
    vec4 clipSpacePos = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); // Convert to clip space
    vec4 viewSpacePos = projInv * clipSpacePos; // Transform to view space using inverse projection matrix
    return viewSpacePos.xyz / viewSpacePos.w; // Perspective division
}

float LinearizeDepth(float depthValue, float nearPlane, float farPlane) {
    // Back-convert the depth to the non-linear value 
    float z = depthValue * 2.0 - 1.0; // Transform from [0, 1] to [-1, 1] (NDC space)

    // Reverse perspective projection to linear view space depth
    return (2.0 * nearPlane * farPlane) / (farPlane + nearPlane - z * (farPlane - nearPlane));
}

void main() {
    float near = 0.1;
    float far = 100.0;
    float depth = texture(gDepth, TexCoords).r; // Fetch depth from depth buffer
    depth = 1-((LinearizeDepth(depth, near, far)-near)/(far-near));
    vec3 normal = normalize(texture(gNormal, TexCoords).rgb); // Fetch and normalize normal
    vec3 viewPos = getViewPos(TexCoords, depth, projUBO.projInv);

    // Sample a random vector for dithering
    vec3 randomVec = normalize(texture(texNoise, TexCoords * inputsUBO.noiseScale).xyz);

    vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
    vec3 bitangent = cross(normal, tangent);
    mat3 TBN = mat3(tangent, bitangent, normal);
    float adjustedRadius = inputsUBO.radius;

    float occlusion = 0.0;
    for (int i = 0; i < inputsUBO.kernelSize; ++i) {
        // Transform the sample vector to the view space
        vec3 samplePos = TBN * samplesUBO.samples[i];
        samplePos = viewPos + samplePos * adjustedRadius;

        // Project the sample position
        vec4 offset = projUBO.proj * vec4(samplePos, 1.0);
        offset.xy /= offset.w; // Perspective division
        offset.xy = offset.xy * 0.5 + 0.5; // Transform to [0,1]

        // Fetch the depth of the sample position
        float sampleDepth = texture(gDepth, offset.xy).r;
        sampleDepth = 1-((LinearizeDepth(sampleDepth, near, far)-near)/(far-near));
        float rangeCheck = smoothstep(0.0, 1.0, adjustedRadius / abs(viewPos.z - sampleDepth));

        occlusion += (sampleDepth >= samplePos.z + inputsUBO.bias ? 1.0 : 0.0) * rangeCheck;
    }
    
    // Average occlusion value for the fragment
    occlusion = 1.0 - (occlusion / float(inputsUBO.kernelSize));
    FragColor = vec4(vec3(occlusion), 1.0); // Output as greyscale
}