SSAO not working as expected (SOLVED)

Hey, guys!

I tried to implement SSAO yesterday and I thought it was working, but then I realized it wasn’t :frowning:

Basically what’s happening is that as the camera moves away from the object, the object begins to fade to white, but when I move closer, the object gets darker and darker.

I think I know what the problem is. Basically, when I move closer, the depth values of the fragments decrease, so it gets darker. But when I move further away, the depth values of the fragments increase, so it appears to be whiter. But that isn’t how SSAO is supposed to work.

Here are some pics:

https://anuj-rao.tinytake.com/sf/OTUxMzkyXzM5ODk2OTM

^When the camera is close to the Stanford dragon model

https://anuj-rao.tinytake.com/sf/OTUxMzkzXzM5ODk2OTQ

^When the camera moves away from the Standford dragon model

You see, it doesn’t look right. Also, the corners are lighter than the actual surfaces??!!!

It looks all wrong!

Here’s my SSAO fragment shader:


#version 400 core

in vec2 passTextureCoords;

out vec4 fragColour;

uniform sampler2D gPositionDepth;
uniform sampler2D gNormals;
uniform sampler2D noiseTexture;

uniform vec3 samples[16];
uniform mat4 projMatrix;

const vec2 noiseScale = vec2(1280/4, 780/4);

void main(void){
	int kernelSize = 16;
	float radius = 1;	

	vec3 fragPos = texture(gPositionDepth, passTextureCoords).xyz;
	vec3 normal = texture(gNormals, passTextureCoords).xyz;
	vec3 randomVec = texture(noiseTexture, passTextureCoords * noiseScale).xyz;
	
	normal = normalize(normal);
	vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
	vec3 bitangent = normalize(cross(normal, tangent));
	mat3 TBN = mat3(tangent, bitangent, normal);
	
	float occlusion = 0;
	for(int i = 0; i < kernelSize; i++){
		vec3 samp = TBN * samples[i];
		samp = fragPos + samp * radius;
		
		vec4 offset = vec4(samp, 1);
		offset = projMatrix * offset;
		offset.xyz /= offset.w;
		offset.xyz = offset.xyz * 0.5 + 0.5;
		
		float sampleDepth = texture(gPositionDepth, offset.xy).w;
		float rangeCheck = smoothstep(0, 1, radius / abs(fragPos.z - sampleDepth));		
		occlusion += (sampleDepth >= samp.z ? 1 : 0) * rangeCheck;
	}
	
	occlusion = 1 - (occlusion/16);
	fragColour = vec4(vec3(pow(occlusion, 8)), 1);
}

G-buffer vertex shader:


#version 400 core

in vec3 positions;
in vec4 colour;
in vec3 normals;

out vec4 passColour;
out vec3 fragPos;
out vec3 passNormals;

uniform mat4 transMatrix;
uniform mat4 projMatrix;
uniform mat4 viewMatrix;

void main(void){
	gl_Position = projMatrix * viewMatrix * transMatrix * vec4(positions, 1.0);
	passColour = colour;
    passNormals = (viewMatrix * transMatrix * vec4(normals, 0)).xyz;
	fragPos = (viewMatrix * transMatrix * vec4(positions, 1.0)).xyz;
}

G-buffer fragment shader:


#version 400 core

in vec4 passColour;
in vec3 fragPos;
in vec3 passNormals;

layout (location = 0) out vec4 gPositionDepth;
layout (location = 1) out vec4 gNormals;
layout (location = 2) out vec4 gAlbedoSpec;

const float NEAR = 0.01f;
const float FAR = 1000; 
float linearizeDepth(float depth)
{
    float z = depth * 2.0 - 1.0;
    return (2.0 * NEAR * FAR) / (FAR + NEAR - z * (FAR - NEAR));	
}

void main(void){
	gPositionDepth.rgb = fragPos;
	gPositionDepth.a = linearizeDepth(gl_FragCoord.z);
	gNormals = vec4(normalize(passNormals), 1);
	gAlbedoSpec = passColour;
}

And finally, here’s how I generate the hemispherical sample kernel and the random rotation vectors:


package renderer;

import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;

import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;

import util.MathUtils;

public class SSAO {
	
	public static List<Vector3f> getSSAOKernel(){
		List<Vector3f> kernel = new ArrayList<Vector3f>();
		for(int i = 0; i < 16; i++){
			Vector3f sample = new Vector3f(((float)Math.random()) * 2 - 1, ((float)Math.random()) * 2 - 1, ((float)Math.random()));
			sample.normalize();
			sample.mul(((float)Math.random()));
			float scale = 1f/16f;
			scale = MathUtils.lerp(0.1f, 1f, scale * scale);
			sample.mul(scale);
			kernel.add(sample);
		}
		return kernel;
	}
	
	public static int createSSAONoiseTexture(){
		List<Vector3f> ssaoNoise = new ArrayList<Vector3f>();
		for(int i = 0; i < 16; i++){
			Vector3f noise = new Vector3f(((float)Math.random()) * 2 - 1, ((float)Math.random()) * 2 - 1, 0);
			noise.normalize();
			ssaoNoise.add(noise);
		}
		int noisetexture = GL11.glGenTextures();
		Loader.TEXTURE_LIST.add(noisetexture);
		FloatBuffer buffer = BufferUtils.createFloatBuffer(ssaoNoise.size() * 3);
		for(Vector3f v : ssaoNoise){
			buffer.put(v.x);
			buffer.put(v.y);
			buffer.put(v.z);
		}
		buffer.flip();
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, noisetexture);
		GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL30.GL_RGB16F, 4, 4, 0, GL11.GL_RGB, GL11.GL_FLOAT, buffer);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
		return noisetexture;
	}

}

I’m following the tutorial from learnopengl.com

A little help would really be appreciated. I’ve already scoured the Internet for answers and I can’t think of any myself. Thanks :slight_smile:

Have you debugged your GBuffer to make sure it’s correct?
Show some pictures. Including your linearized depth buffer.

I’ve written at least 4-5 SSAO implementations so far, hopefully I can help! :slight_smile:

orange451:

here’s the vertex position texture (in view space):

https://anuj-rao.tinytake.com/sf/OTUxODQyXzM5OTI1MTM

here’s the normal texture (also in view space):

https://anuj-rao.tinytake.com/sf/OTUxODQ4XzM5OTI1Mjk

And here’s the linearized depth texture:

https://anuj-rao.tinytake.com/sf/OTUxODU2XzM5OTI1Mzc

The linearized depth values look completely white, but when I move really close to the model, it gets darker and turns almost black.

Here’s my linearization formula:

(2.0 * NEAR * FAR) / (FAR + NEAR - z * (FAR - NEAR))

The z value is in normalized device coordinates.

Thanks :slight_smile:

Fixed it. I changed

sampleDepth = -texture(gPositionDepth, offset.xy).w;

to

sampleDepth = texture(gPositionDepth, offset.xy).z;

Results:

https://anuj-rao.tinytake.com/sf/OTUyMDM1XzM5OTMwNzE

Yay :slight_smile:

Glad you got it working! :slight_smile: