I am building a 3d renderer in C++ using Vulkan.hpp. I am in the process of implementing a SSAO stage into the rendering pipeline. I have managed to set everything up and have a working SSAO stage. However, I just want someone to check the following code for the SSAO fragment shader and verify everything looks right.

Inputs to the InputsBufferObject:

kernelSize = 64
radius = 0.5
bias = 0.025
noiseScale = swapChainImage / 8

Noise and sample generation process:

    for (unsigned int i = 0; i < ssaoSamples; ++i)
    {
        glm::vec3 sample(
            randomFloats(generator) * 2.0 - 1.0,
            randomFloats(generator) * 2.0 - 1.0,
            randomFloats(generator) * 2.0 - 1.0
        );

        // Use hemisphere sampling instead of full sphere
        if (sample.z < 0.0f)
            sample.z = -sample.z;

        sample = glm::normalize(sample);

        // Improved scale distribution for better sample spread
        float scale = pow((float)i / ssaoSamples, 2.0f);
        scale = 0.5f * (1.0f - cosf(scale * M_PI));  // Cosine distribution

        // Scale the sample and ensure proper normalization
        ssaoKernel.push_back(sample);
    }


    for (unsigned int i = 0; i < ssaoNoiseSize * ssaoNoiseSize; i++)
    {
        glm::vec4 noise(
            randomFloats(generator) * 2.0 - 1.0,
            randomFloats(generator) * 2.0 - 1.0,
            randomFloats(generator) * 2.0 - 1.0,
            1.0f
        );

        // Normalize and scale the noise
        noise = glm::normalize(noise)*0.1f;  // Reduced noise scale
        ssaoNoise.push_back(noise);
    }

SSAO fragment shader code:

#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;

vec3 reconstructVSPosFromDepth(vec2 uv)
{
    float nearPlane = 0.1;
    float farPlane = 100.0;
	float depth = texture(gDepth, uv).r;
        float linearSampleDepth = (2.0 * nearPlane) / 
                                 (farPlane + nearPlane - 
                                  depth * (farPlane - nearPlane));
        linearSampleDepth = normalize(linearSampleDepth); 
	float x = uv.x * 2.0f - 1.0f;
	float y = (1.0f - uv.y) * 2.0f - 1.0f;
	vec4 pos = vec4(x, y, linearSampleDepth, 1.0f);
	vec4 posVS = projUBO.projInv * pos;
	vec3 posNDC = posVS.xyz / posVS.w;
	return posNDC;
}

void main() {
	float nearPlane = 0.1;
	float farPlane = 100.0;

	float depth = texture(gDepth, TexCoords).r;

	vec3 posVS = reconstructVSPosFromDepth(TexCoords);

	vec3 normal = normalize(texture(gNormal, TexCoords).rgb);
	vec3 randomVec = normalize(texture(texNoise, TexCoords * inputsUBO.noiseScale).xyz);
	
	vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
	vec3 bitangent = cross(tangent, normal);
	mat3 TBN = mat3(tangent, bitangent, normal);

	float occlusion = 0.0f;
	for (uint i = 0; i < inputsUBO.kernelSize; i++)
	{
		vec3 samplePos = TBN * samplesUBO.samples[i].xyz;
		samplePos = posVS + samplePos * inputsUBO.radius; 

		vec4 offset = vec4(samplePos, 1.0f);
		offset = projUBO.proj * offset;
		offset.xyz /= offset.w;
		offset.xyz = offset.xyz * 0.5f + 0.5f;
		//offset.y = 1.0f - offset.y;
		
		vec3 reconstructedPos = reconstructVSPosFromDepth(offset.xy);

        
        // range check & accumulate
        float rangeCheck = smoothstep(0.0, 1.0, inputsUBO.radius / abs(posVS.z - reconstructedPos.z));
        occlusion += (reconstructedPos.z >= samplePos.z + inputsUBO.bias ? 1.0 : 0.0) * rangeCheck;     

	}
	occlusion = (occlusion / inputsUBO.kernelSize);
	

    FragColor = vec4(occlusion,occlusion,occlusion,1.0);

}