openglglslopentkssao

OpenGL - strange SSAO artifact


I followed the tutorial at Learn OpenGL to implement Screenspace Ambient Occlusion. Things are mostly looking okay besides a strange artifact at the top and bottom of the window.

The problem is more obvious moving the camera, when it appears as if top parts of the image are imprinted on the bottom and vise versa, as shown in this video.

enter image description here

The artifact worsens when standing close to a wall and looking up and down so perhaps the Znear value is contributing? The scale of my scene does seem small compared to other demos, Znear and Zfar are 0.01f and 1000 and the width of the shown hallway is around 1.2f.

I've read into the common SSAO artifacts and haven't found anything resembling this.

#version 330 core

in vec2 TexCoords;
layout (location = 0) out vec3 FragColor;   

uniform sampler2D MyTexture0;   // Position
uniform sampler2D MyTexture1;   // Normal
uniform sampler2D MyTexture2;   // TexNoise

const int samples = 64;
const float radius = 0.25;
const float bias = 0.025;

uniform mat4 projectionMatrix;

uniform float screenWidth;
uniform float screenHeight;

void main()
{
    //tile noise texture over screen based on screen dimensions divided by noise size
    vec2 noiseScale = vec2(screenWidth/4.0, screenHeight/4.0); 

    vec3 sample_sphere[64];
    sample_sphere[0] = vec3(0.04977, -0.04471, 0.04996);
    sample_sphere[1] = vec3(0.01457, 0.01653, 0.00224);
    sample_sphere[2] = vec3(-0.04065, -0.01937, 0.03193);
    sample_sphere[3] = vec3(0.01378, -0.09158, 0.04092);
    sample_sphere[4] = vec3(0.05599, 0.05979, 0.05766);
    sample_sphere[5] = vec3(0.09227, 0.04428, 0.01545);
    sample_sphere[6] = vec3(-0.00204, -0.0544, 0.06674);
    sample_sphere[7] = vec3(-0.00033, -0.00019, 0.00037);
    sample_sphere[8] = vec3(0.05004, -0.04665, 0.02538);
    sample_sphere[9] = vec3(0.03813, 0.0314, 0.03287);
    sample_sphere[10] = vec3(-0.03188, 0.02046, 0.02251);
    sample_sphere[11] = vec3(0.0557, -0.03697, 0.05449);
    sample_sphere[12] = vec3(0.05737, -0.02254, 0.07554);
    sample_sphere[13] = vec3(-0.01609, -0.00377, 0.05547);
    sample_sphere[14] = vec3(-0.02503, -0.02483, 0.02495);
    sample_sphere[15] = vec3(-0.03369, 0.02139, 0.0254);
    sample_sphere[16] = vec3(-0.01753, 0.01439, 0.00535);
    sample_sphere[17] = vec3(0.07336, 0.11205, 0.01101);
    sample_sphere[18] = vec3(-0.04406, -0.09028, 0.08368);
    sample_sphere[19] = vec3(-0.08328, -0.00168, 0.08499);
    sample_sphere[20] = vec3(-0.01041, -0.03287, 0.01927);
    sample_sphere[21] = vec3(0.00321, -0.00488, 0.00416);
    sample_sphere[22] = vec3(-0.00738, -0.06583, 0.0674);
    sample_sphere[23] = vec3(0.09414, -0.008, 0.14335);
    sample_sphere[24] = vec3(0.07683, 0.12697, 0.107);
    sample_sphere[25] = vec3(0.00039, 0.00045, 0.0003);
    sample_sphere[26] = vec3(-0.10479, 0.06544, 0.10174);
    sample_sphere[27] = vec3(-0.00445, -0.11964, 0.1619);
    sample_sphere[28] = vec3(-0.07455, 0.03445, 0.22414);
    sample_sphere[29] = vec3(-0.00276, 0.00308, 0.00292);
    sample_sphere[30] = vec3(-0.10851, 0.14234, 0.16644);
    sample_sphere[31] = vec3(0.04688, 0.10364, 0.05958);
    sample_sphere[32] = vec3(0.13457, -0.02251, 0.13051);
    sample_sphere[33] = vec3(-0.16449, -0.15564, 0.12454);
    sample_sphere[34] = vec3(-0.18767, -0.20883, 0.05777);
    sample_sphere[35] = vec3(-0.04372, 0.08693, 0.0748);
    sample_sphere[36] = vec3(-0.00256, -0.002, 0.00407);
    sample_sphere[37] = vec3(-0.0967, -0.18226, 0.29949);
    sample_sphere[38] = vec3(-0.22577, 0.31606, 0.08916);
    sample_sphere[39] = vec3(-0.02751, 0.28719, 0.31718);
    sample_sphere[40] = vec3(0.20722, -0.27084, 0.11013);
    sample_sphere[41] = vec3(0.0549, 0.10434, 0.32311);
    sample_sphere[42] = vec3(-0.13086, 0.11929, 0.28022);
    sample_sphere[43] = vec3(0.15404, -0.06537, 0.22984);
    sample_sphere[44] = vec3(0.05294, -0.22787, 0.14848);
    sample_sphere[45] = vec3(-0.18731, -0.04022, 0.01593);
    sample_sphere[46] = vec3(0.14184, 0.04716, 0.13485);
    sample_sphere[47] = vec3(-0.04427, 0.05562, 0.05586);
    sample_sphere[48] = vec3(-0.02358, -0.08097, 0.21913);
    sample_sphere[49] = vec3(-0.14215, 0.19807, 0.00519);
    sample_sphere[50] = vec3(0.15865, 0.23046, 0.04372);
    sample_sphere[51] = vec3(0.03004, 0.38183, 0.16383);
    sample_sphere[52] = vec3(0.08301, -0.30966, 0.06741);
    sample_sphere[53] = vec3(0.22695, -0.23535, 0.19367);
    sample_sphere[54] = vec3(0.38129, 0.33204, 0.52949);
    sample_sphere[55] = vec3(-0.55627, 0.29472, 0.3011);
    sample_sphere[56] = vec3(0.42449, 0.00565, 0.11758);
    sample_sphere[57] = vec3(0.3665, 0.00359, 0.0857);
    sample_sphere[58] = vec3(0.32902, 0.0309, 0.1785);
    sample_sphere[59] = vec3(-0.08294, 0.51285, 0.05656);
    sample_sphere[60] = vec3(0.86736, -0.00273, 0.10014);
    sample_sphere[61] = vec3(0.45574, -0.77201, 0.00384);
    sample_sphere[62] = vec3(0.41729, -0.15485, 0.46251);
    sample_sphere[63] = vec3 (-0.44272, -0.67928, 0.1865);

    // get input for SSAO algorithm
    vec3 fragPos = texture(MyTexture0, TexCoords).xyz;
    vec3 normal = normalize(texture(MyTexture1, TexCoords).rgb);
    vec3 randomVec = normalize(texture(MyTexture2, TexCoords * noiseScale).xyz);

    // create TBN change-of-basis matrix: from tangent-space to view-space
    vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
    vec3 bitangent = cross(normal, tangent);
    mat3 TBN = mat3(tangent, bitangent, normal);

    // iterate over the sample kernel and calculate occlusion factor
    float occlusion = 0.0;
    for(int i = 0; i < samples; ++i)
    {
        // get sample position
        vec3 sample = TBN * sample_sphere[i]; // from tangent to view-space
        sample = fragPos + sample * radius; 

        // project sample position (to sample texture) (to get position on screen/texture)
        vec4 offset = vec4(sample, 1.0);
        offset = projectionMatrix * offset; // from view to clip-space
        offset.xyz /= offset.w; // perspective divide
        offset.xyz = offset.xyz * 0.5 + 0.5; // transform to range 0.0 - 1.0

        // get sample depth
        float sampleDepth = texture(MyTexture0, offset.xy).z;

        // range check & accumulate
        float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth));
        occlusion += (sampleDepth >= sample.z + bias ? 1.0 : 0.0) * rangeCheck;           
    }
    occlusion = 1.0 - (occlusion / samples);

    FragColor = vec3(occlusion);
}

Solution

  • As Rabbid76 suggested, the artifacts were caused by sampling outside of the screen borders. I added a check to prevent this and things are looking much better..

    vec4 clipSpacePos = projectionMatrix * vec4(sample, 1.0); // from view to clip-space
    vec3 ndcSpacePos = clipSpacePos.xyz /= clipSpacePos.w; // perspective divide
    vec2 windowSpacePos = ((ndcSpacePos.xy + 1.0) / 2.0) * vec2(screenWidth, screenHeight);
    
    if ((windowSpacePos.y > 0) && (windowSpacePos.y < screenHeight))
        if ((windowSpacePos.x > 0) && (windowSpacePos.x < screenWidth))
            // THEN APPLY AMBIENT OCCLUSION
    

    enter image description here

    It hasn't entirely fixed the issue though as areas close to the windows edge now appear lighter than they should because fewer samples are tested. Perhaps somebody can suggest an approach that moves the sample area to an appropriate location?