SSAO artifacts generated by random vectors

180 Views Asked by At

I recently tried to implement SSAO algorithm but encounter some strange artifacts.

I am 99% sure that the artifacts are generated by the random vectors after a few days' tuning. Like most implementation, the random vectors are obtained by sampling a 4x4 noise texture with GL_REPEAT parameter.

The artifacts look like below, see those vertical stripe while rotating the camera.

(The radius was increased to a large amount to amplify the artifacts, but you can still notice the artifacts with a small radius)

ssao-artifacts

See below, my SSAO is done by a compute shader where the u_out_image store the final ssao output and ComputeOcclusion computes the occlusion from gbuffer.

void main()
{
    ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
    ivec2 size = imageSize(u_out_image);
    if (pos.x >= size.x || pos.y >= size.y) {
        return;
    }
    vec2 uv = vec2(pos) / size;
    vec2 random_scale = vec2(size) / 4.f;
    vec3 random_vec = normalize(texture(u_noise, uv * random_scale).xyz);
    float occlusion = ComputeOcclusion(random_vec, uv);
    imageStore(u_out_image, pos, vec4(pow(occlusion, u_power)));
}

If I don't include the noise texture(u_noise), the artifacts will not exist. And depending on the random_scale, the artifacts will be worse or better. So I am pretty sure it is to blame. Is this nomral in SSAO? Can somebody explain why this is happening? Thanks in advance.

EDIT:Here's the full shader code as requested.

#version 450 core
layout(local_size_x = 25, local_size_y = 25) in;
layout(r16f) uniform image2D u_out_image;

layout(std140) uniform Camera
{
    mat4 u_projection;
    mat4 u_view;
};


uniform sampler2D u_position;
uniform sampler2D u_normal;

const uint KERNEL_SIZE = 64;
uniform uint u_kernel_size = KERNEL_SIZE;

uniform sampler2D u_noise;
uniform vec3 u_samples[KERNEL_SIZE];
uniform float u_radius;
uniform float u_bias;
uniform uint u_power;

float ComputeOcclusion(vec3 random_vec, vec2 uv)
{
    // get input for SSAO algorithm
    vec3 frag_pos = texture(u_position, uv).xyz;
    vec3 normal = texture(u_normal, uv).xyz;
    if (normal == vec3(0)) return 1;

    frag_pos = (u_view * vec4(frag_pos, 1.0f)).xyz;
    mat3 normal_matrix = transpose(inverse(mat3(u_view)));
    normal = normalize(normal_matrix * normal);

    // create TBN change-of-basis matrix: from tangent-space to view-space
    vec3 tangent = normalize(random_vec - normal * dot(random_vec, normal));
    vec3 bi_tangent = cross(normal, tangent);
    mat3 TBN = mat3(tangent, bi_tangent, normal);
    // iterate over the sample kernel and calculate occlusion factor
    float occlusion = 0.0;
    for (int i = 0; i < u_kernel_size; ++i) {
        // get sample position
        vec3 sample_pos = TBN * u_samples[i];  // from tangent to view-space
        if(dot(sample_pos, normal) < 0.15) {
            continue;
        }
        sample_pos = frag_pos + sample_pos * u_radius;

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

        if (offset.x < 0 || offset.y < 0 || offset.x > 1 || offset.y > 1) {
            continue;
        }
        // get sample depth
        float sample_depth =
            (u_view * vec4(texture(u_position, offset.xy).xyz, 1.0f)).z;
        if ((normal_matrix * texture(u_normal, offset.xy).xyz) == vec3(0)) {
            continue;
        }

        // range check & accumulate
        float factor = u_radius / abs(frag_pos.z - sample_depth);
        float range_check = smoothstep(0.0, 1.0, factor);
        occlusion +=
            (sample_depth >= sample_pos.z + u_bias ? 1.0 : 0.0) * range_check;
    }
    return 1 - occlusion / u_kernel_size;
}

void main()
{
    ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
    ivec2 size = imageSize(u_out_image);
    if (pos.x >= size.x || pos.y >= size.y) {
        return;
    }
    vec2 uv = vec2(pos) / size;
    vec2 random_scale = vec2(size) / 4.f;
    vec3 random_vec = normalize(texture(u_noise, uv * random_scale).xyz);
    float occlusion = ComputeOcclusion(random_vec, uv);
    imageStore(u_out_image, pos, vec4(pow(occlusion, u_power)));
}
0

There are 0 best solutions below