2

I am trying to implement SSAO based on this tutorial: http://john-chapman-graphics.blogspot.com/2013/01/ssao-tutorial.html. I don't seem to understand how to achieve this and I keep getting undesired results in my fragment shader.

First I setup my shader to pass in the depth buffer, camera's projection matrix and 4x4 noise texture. The ssao kernel has already been initialized and passed into the shader as well. I also sample the normal from the depth buffer.

EDIT: At first I thought I was getting the depth position incorrectly so I added a new function called positionFromDepth, but it still doesn't seem to work...

My fragment shader:

uniform sampler2D uDepthBuffer;
uniform sampler2D uNoiseTexture;

uniform mat4 uProjection; // camera's projection matrix
uniform mat4 uInverseMatrix; // inverse of projection matrix
uniform vec2 uNoiseScale; // vec2(1024.0 / 4.0, 768.0 / 4.0)

const int MAX_KERNEL_SIZE = 128;

uniform int uSampleKernelSize;
uniform vec3 uSampleKernel[MAX_KERNEL_SIZE];

uniform float uRadius;

const float zNear = 0.1;
const float zFar = 2579.5671;

float linearizeDepth(float near, float far, float depth) {
    return 2.0 * near / (far + near - depth * (far - near));
}

vec3 positionFromDepth(vec2 texcoords) {
    float d = linearizeDepth(zNear, zFar, texture2D(uDepthBuffer, texcoords).r);
    vec4 pos = uInverseMatrix * vec4(texcoords.x * 2.0 - 1.0,
                                     texcoords.y * 2.0 - 1.0,
                                     d * 2.0 - 1.0, 1.0);

    pos.xyz /= pos.w;

    return pos.xyz;
}

vec3 normal_from_depth(float depth, vec2 texcoords) {
  const vec2 offset1 = vec2(0.0, 0.001);
  const vec2 offset2 = vec2(0.001, 0.0);

  float depth1 = linearizeDepth(zNear, zFar, texture2D(uDepthBuffer, texcoords + offset1).r);
  float depth2 = linearizeDepth(zNear, zFar, texture2D(uDepthBuffer, texcoords + offset2).r);

  vec3 p1 = vec3(offset1, depth1 - depth);
  vec3 p2 = vec3(offset2, depth2 - depth);

  vec3 normal = cross(p1, p2);
  normal.z = -normal.z;

  return normalize(normal);
}

void main() {
    vec2 texcoord = gl_TexCoord[0].st;

    vec3 origin = positionFromDepth(texcoord);

    float d = texture2D(uDepthBuffer, texcoord).r;
    vec3 normal = normal_from_depth(linearizeDepth(zNear, zFar, d), texcoord) * 2.0 - 1.0;
    normal = normalize(normal);

    vec3 rvec = texture2D(uNoiseTexture, texcoord * uNoiseScale).rgb * 2.0 - 1.0;
    vec3 tangent = normalize(rvec - normal * dot(rvec, normal));
    vec3 bitangent = cross(normal, tangent);
    mat3 tbn = mat3(tangent, bitangent, normal);

    float occlusion = 0.0;

    for(int i = 0; i < uSampleKernelSize; i++) {
        vec3 sample = tbn * uSampleKernel[i];
        sample = sample * uRadius + origin.xyz;

        vec4 offset = vec4(sample, 1.0);
        offset = uProjection * offset;
        offset.xy /= offset.w;
        offset.xy = offset.xy * 0.5 + 0.5;

        float sampleDepth = positionFromDepth(offset.xy).z;
        occlusion += (sampleDepth <= sample.z ? 1.0 : 0.0);
    }

    occlusion = 1.0 - (occlusion / float(uSampleKernelSize));
    gl_FragColor = vec4(pow(occlusion, 4.0));
}

The occluded samples changes when I move my camera around. The result looks something like this:

Result from SSAO shader

I am assuming I am doing something wrong when sampling the depth position, but maybe I am doing something wrong with the projection matrix as well. Should the uProjection matrix be transposed or inverted? Can anyone identity the issue?

Also ignore the fact that I haven't done a blur pass yet.

Villsa
  • 81
  • 4
  • This should not even compile, the third non-whitespace line in `main (...)` references a variable `d`, which does not exist at that scope. – Andon M. Coleman May 13 '14 at 23:53
  • Sorry about that, I made a typo when I was updating the shader from last time. Re-added the missing line. – Villsa May 14 '14 at 02:04
  • Well, from the looks of things, the value of `d` needs to be linearized before passing it to that function. – Andon M. Coleman May 14 '14 at 02:07
  • Looks like I forgot to call the linearizeDepth function. Though it still doesn't seem to resolve the problem. Maybe it's the linearizeDepth function itself that's incorrectly computing the linearized depth? – Villsa May 14 '14 at 02:58
  • That is a possibility, but I was referring to the change you made to the code in the question -- `d` is not linearized. – Andon M. Coleman May 14 '14 at 03:00
  • Updated the fragment shader once again. Still getting very odd results as seen in the picture, unfortunately. – Villsa May 14 '14 at 03:05

1 Answers1

0

A couple of things to consider here. You've substituted the original linearizeDepth equation with something that different. The original linearizeDepth function is based on this formula.

Note that if you attempt to derive the function based on this formula you'll see that it's actually the inverse of Z that the linearizeDepth function returns not just Z.

I cannot say to what extent this will affect the SSAO algorithm but given what I've learned from studying the code myself, I'd be very careful tinkering with parts of the equation that I don't completely understand. Essentially, I'm not sure that the code does with it's supposed to any longer.

Specifically, I believe the purpose of linearizeDepth in this instance is not to evenly distribute light and dark (I believe, that's what you're doing here), in the z axis, but rather cheaply unproject the Z' coordinate.

John Leidegren
  • 59,920
  • 20
  • 131
  • 152