5
\$\begingroup\$

I'm attempting to implement screen-space ambient occlusion by the method shown here. My SSAO shader program is the one part that isn't working. Here are the outputs of the initial rendering pass, captured in renderdoc:

GL_COLOR_ATTACHMENT0, diffuse textures without lighting: enter image description here

GL_COLOR_ATTACHMENT1, surface normals in view space: enter image description here

GL_DEPTH_ATTACHMENT, depth buffer: enter image description here

These are passed to the SSAO shader program, but it only produces white output (no occlusion): enter image description here

This is the SSAO shader source.

Vertex shader:

#version 330

layout (location = 0) in vec2 v_position;
layout (location = 1) in vec2 v_texcoord;

out vec2 f_texcoord;

void main() {
    f_texcoord = v_texcoord;
    gl_Position = vec4(v_position.xy, 1.0f, 1.0f);
}

Fragment shader:

#version 330

#define KERNEL_SIZE 32
#define Z_DELTA_MIN 0.0001f
#define Z_DELTA_MAX 0.005f

in vec2 f_texcoord;

out vec4 color;

uniform sampler2D ssao_tex;
uniform sampler2D ssao_normal_tex;
uniform sampler2D ssao_depth_tex;
uniform vec3[KERNEL_SIZE] ssao_kernel;
uniform sampler2D ssao_noise_tex;
uniform vec2 ssao_noise_scale;
uniform mat4 ssao_projection_matrix;
uniform mat4 ssao_inverse_projection_matrix;

vec4 get_view_position(vec2 texcoord) {
    // scale-bias texcoords from [0, 1] to [-1, 1] to retrieve NDC x and y
    float x = texcoord.s * 2.0f - 1.0f;
    float y = texcoord.t * 2.0f - 1.0f;

    // pull NDC z out of the depth buffer
    // EDITED: was erroneously scaling z coordinate
    float z = texture(ssao_depth_tex, texcoord).r;

    vec4 ndc_position = vec4(x, y, z, 1.0f);

    // calculate this fragment's position in view space
    vec4 view_position = ssao_inverse_projection_matrix * ndc_position;
    return view_position / view_position.w;
}

void main() {
    vec4 view_position = get_view_position(f_texcoord);
    vec3 view_normal = normalize(texture(ssao_normal_tex, f_texcoord).xyz * 2.0 - 1.0);
    vec3 rotation = normalize(texture(ssao_noise_tex, f_texcoord * ssao_noise_scale).xyz * 2.0 - 1.0);
    vec3 view_tangent = normalize(rotation - view_normal * dot(rotation, view_normal));
    vec3 view_bitangent = cross(view_normal, view_tangent);
    mat3 kernel_matrix = mat3(view_tangent, view_bitangent, view_normal);

    float occlusion = 0.0f;
    for (int i = 0; i < KERNEL_SIZE; i++) {
        vec4 view_sample = view_position + 1.0f * vec4(kernel_matrix * ssao_kernel[i], 1.0f);
        vec4 ndc_sample = ssao_projection_matrix * view_sample;
        ndc_sample.xy /= ndc_sample.w;

        // scale-bias back from [-1, 1] to [0, 1]
        vec2 sample_texcoord = ndc_sample.xy * 0.5 + 0.5;
        float ndc_depth = texture(ssao_depth_tex, sample_texcoord).r;
        if (abs(view_position.z - ndc_depth) < 1.0f && ndc_depth < view_sample.z) {
            occlusion += 1.0f;
        }
    }

    occlusion /= float(KERNEL_SIZE);
    occlusion = 1.0f - occlusion;
    color = vec4(occlusion, occlusion, occlusion, 1.0f);

    // view-space surface normals
    // color = vec4(view_normal.xyz * 0.5 + 0.5, 1.0f);
    // combined SSAO + model textures
    // color = texture(ssao_tex, f_texcoord) * vec4(occlusion, occlusion, occlusion, 1.0f);
}

The kernel and noise are generated like so:

std::mt19937 gen;
std::uniform_real_distribution<GLfloat> zero_one_dist(0.0f, 1.0f);
std::uniform_real_distribution<GLfloat> minus_one_one_dist(-1.0f, 1.0f);

/*
 * SSAO sample kernel generation
 */
GLfloat ssao_kernel[3 * KERNEL_SIZE];
for (size_t i = 0; i < KERNEL_SIZE; i++) {
    /*
     * Generate points at random across the surface of a hemisphere on the
     * z-axis with radius r = 1. Initially generate x and y on the range
     * [-1, 1] and z on the range [0, 1], then normalize the vector
     */
    float x = minus_one_one_dist(gen);
    float y = minus_one_one_dist(gen);
    float z = zero_one_dist(gen);
    float mag = sqrtf(x * x + y * y + z * z);

    ssao_kernel[3 * i] = x / mag;
    ssao_kernel[3 * i + 1] = y / mag;
    ssao_kernel[3 * i + 2] = z / mag;

    /*
     * Scatter the points inside the hemisphere using an attenuation
     * function to place more samples closer to the origin
     */
    float scale = (float)i / (float)KERNEL_SIZE;
    scale = LERP(0.1f, 1.0f, scale);
    for (size_t c = 0; c < 3; c++) {
        ssao_kernel[3 * i + c] *= scale;
    }
}

/*
 * Random noise generation for kernel rotation
 */
GLfloat ssao_noise[3 * NOISE_SIZE];
for (size_t i = 0; i < NOISE_SIZE; i++) {
    /*
     * Generate a small noise texture used for randomly rotating the sample
     * points about the z-axis
     */
    float x = minus_one_one_dist(gen);
    float y = minus_one_one_dist(gen);
    float z = 0.0;
    float mag = sqrtf(x * x + y * y + z * z);

    ssao_noise[3 * i] = x / mag;
    ssao_noise[3 * i + 1] = y / mag;
    ssao_noise[3 * i + 2] = z / mag;
}

GLuint ssao_noise_tex;
glGenTextures(1, &ssao_noise_tex);
glBindTexture(GL_TEXTURE_2D, ssao_noise_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, NOISE_SIZE_1D, NOISE_SIZE_1D, 0, GL_RGB, GL_FLOAT, ssao_noise);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, 0);

GLfloat ssao_noise_scale[2] = { 1920.0f / (GLfloat)NOISE_SIZE_1D,
                                1080.0f / (GLfloat)NOISE_SIZE_1D };

The full source code of the program is available here.

My assumption is that I'm making an incorrect calculation during the occlusion stage of the fragment shader, but I don't know where that would be. I've checked over the sample calculations and I can't find anything wrong with them. Not quite sure what to do next.

\$\endgroup\$
  • 1
    \$\begingroup\$ Why are you scaling the sampled depth buffer value? float z = texture(ssao_depth_tex, texcoord).r * 2.0f - 1.0f; \$\endgroup\$ – Ian Young Nov 8 '17 at 12:02
  • \$\begingroup\$ @IanYoung that's a mistake, thanks for catching it. Unfortunately the output looks the same, all white screen. \$\endgroup\$ – Cormac O'Brien Nov 8 '17 at 22:23
  • \$\begingroup\$ I think you're creating a w of 1.0f .. 2.0f here: vec4 view_sample = view_position + 1.0f * vec4(kernel_matrix * ssao_kernel[i], 1.0f); or is that intended? \$\endgroup\$ – Stephane Hockenhull Nov 9 '17 at 4:50
  • \$\begingroup\$ I have to say, your depth buffer looks a bit bright. Check your settings when you create it. It might just be that the SSAO shader simply doesn't have enough variance of data to work properly. \$\endgroup\$ – Ian Young Nov 9 '17 at 9:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.