Search code examples
c++openglssao

Crytek SSAO Algorithm OpenGL


I am trying to implement the simple variant of Crytek's screen space ambient occlusion algorithm.

The algorithm as far as I understand it;

  1. For a pixel, p, sample around p in a sphere in view space.
  2. Project the sampled point sp to screen space.
  3. Compare the depth of the sampled point to the current pixel's depth.

That should basically be all there is to it. If the sampled point's depth is higher (it lies beyond the geometry) it does not occluded the current pixel (p).

float z = gl_FragCoord.z; // depth-buffer value for the current pixel
int occluding_points = 0;
vec4 fPosition = model_transformation * vec4(position, 1.0f); // Really from vertex shader
#ifdef CRYTEK_AO
    const int NUM_SAMPLES = 10;
    float R = 3.0f;
    const float[10] steps = float[](0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f);
    for (int sample_id = 0; sample_id < NUM_SAMPLES; sample_id++) {
        // 1. Generate sample point in world space.
        float ang = steps[sample_id];
        vec4 sample_point = vec4(R * cos(2 * M_PI * ang) * sin(M_PI * ang) + fPosition.x,
                                 R * sin(2 * M_PI * ang) * sin(M_PI * ang) + fPosition.y,
                                 R * sin(M_PI * ang) + fPosition.z,
                                 1.0f);
        // 2. Transform sample point from view space to screen space to get its depth value.
        sample_point = projection * camera_view * sample_point; // Clip space
        sample_point = sample_point / sample_point.w;           // Perspective division - Normalized device coordinate
        float sample_depth = 0.5f * (sample_point.z + 1.0f);    // Viewport transform for z - window space

        // 3. Check whether sample_point is behind current pixel depth.
        if (sample_depth > z) { occluding_points++; }
    }
    occlusion_factor = occluding_points / float(NUM_SAMPLES);
    // Diffuse, specular components removed
    total_light += vec3(ambient_intensity) * (1.0f - occlusion_factor); // Ambient factor
    outColor = total_light;
#endif

Below is a screenshot how it looks. For some reason the artifacts only appear when looking down the z-axis so there can be something fishy with the transformations, although the work fine when rendering objects and the camera and so on.

Ambient occlusion gone wrong...

When looking at basically any other angle it looks like you would expect from setting the occlusion factor to 0.5 (which will get you grey in all the color channels).

Nothing abnormal

Result after accidental integer division fixed to floating point number division. enter image description here

Added a vidoe here of the flickering.

Any clues?

EDIT: One problem was detected with rounding. EDIT: Added a video link to the artifact when moving along the z-axis.


Solution

  • There are two fishy things with your code.

    Integer division

    occlusion_factor = occluding_points / NUM_SAMPLES;
    

    Just change type of occluding_points to float and you should be fine.

    Sampling

        vec4 sample_point = vec4(R * cos(2 * M_PI * ang) * sin(M_PI * ang) + fPosition.x,
                                 R * sin(2 * M_PI * ang) * sin(M_PI * ang) + fPosition.y,
                                 R * sin(M_PI * ang) + fPosition.z,
                                 1.0f);
    

    This gives you samples from the same spiral in world coordinates each time, so with right surface you'll get artifacts depending on the viewing angle. That's what I think it's happening when you are looking down the z-axis, when paired with rounding error above.