3
$\begingroup$

I'm having problems with Ambient Occlusion. I've tried to follow both John Chapman's improvements over the Crytek AO and LearnOpenGL's tutorial of it when I failed to properly obtain the correct result. https://i.sstatic.net/fhm8V.jpg

As the camera turns or moves, the screen flashes consistently though it seems arbitrary. Can anyone tell me what I'm doing wrong?

#version 140 in vec2 UV; in vec4 vRay; out vec3 SSAOout; const int kernelSize = 64; uniform mat4 projection; uniform sampler2D gbuffer0; uniform sampler2D gbuffer1; uniform sampler2D gbuffer2; uniform sampler2D gbuffer3; uniform sampler2D noiseTex; uniform vec3 kernels[kernelSize]; vec4 ViewPosFromDepth(float depth, vec2 TexCoord) { float z = depth * 2.0 - 1.0; vec4 clipSpacePosition = vec4(TexCoord * 2.0 - 1.0, z, 1.0); vec4 viewSpacePosition = invProjMat * clipSpacePosition; viewSpacePosition /= viewSpacePosition.w; return viewSpacePosition; } const vec2 noiseScale = vec2(1366.0/4.0, 768.0/4.0); const float near = 0.1; const float far = 100.0; float LinearizeDepth(float depth) { float z = depth * 2.0 - 1.0; // back to NDC return (2.0 * near * far) / (far + near - z * (far - near)); } void main() { vec4 gbuffer0Val = texture(gbuffer0, UV); vec4 gbuffer1Val = texture(gbuffer1, UV); vec4 gbuffer2Val = texture(gbuffer2, UV); vec4 gbuffer3Val = texture(gbuffer3, UV); vec3 fragPos = ViewPosFromDepth(gbuffer0Val.r, UV).rgb; float dist = LinearizeDepth(gbuffer0Val.r); vec3 normal = gbuffer1Val.rgb; normal = decodeNormal(normal); vec3 randomVec = vec3(2 * texture(noiseTex, UV * noiseScale).rg - 1, 0); vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal)); vec3 bitangent = cross(normal, tangent); mat3 TBN = mat3(tangent, bitangent, normal); float bias = 0.025; float radius = 3.0; float occlusion = 0.0; for(int i = 0; i < kernelSize; i++) { vec3 sampleKernel = TBN * kernels[i]; sampleKernel = fragPos + sampleKernel * radius; vec4 offset = vec4(sampleKernel, 1.0); offset = projection * offset; // from view to clip-space offset.xyz /= offset.w; // perspective divide offset.xyz = offset.xyz * 0.5 + 0.5; // transform to range 0.0 - 1.0 float sampleDepth = texture(gbuffer0, 1-offset.xy).r; sampleDepth = LinearizeDepth(sampleDepth); float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth)); occlusion += (sampleDepth >= sampleKernel.z + bias ? 1.0 : 0.0) * rangeCheck; } occlusion = 1.0 - (occlusion / kernelSize); SSAOout = vec3(occlusion); } 
$\endgroup$

1 Answer 1

2
$\begingroup$

You need to be careful about spaces - position and normal need to be in view space.

To transform depth into view space position I used this method. So:

vec3 viewPositionFromDepth(vec2 vTexCoord) { float z = texture2D(depthTexture, vTexCoord).r; // Get x/w and y/w from the viewport position float x = vTexCoord.x * 2.0 - 1.0; float y = vTexCoord.y * 2.0 - 1.0; vec4 vProjectedPos = vec4(x, y, z, 1.0f); // Transform by the inverse projection matrix vec4 vPositionVS = invProjection*vProjectedPos; // Divide by w to get the view-space position return vPositionVS.xyz / vPositionVS.w; } 

To transform normal into view space you need to multiply it by transpose of inverse of view matrix (should be done on CPU):

vec4 n = transpose(inverse(ViewMatrix)) * normalize(texture2D(normalTexture, texCoord)*2.0 - 1.0 ); 

And in loop while sampling depth with offset to compare with kernel position, this depth should be in same space as position so I did it the same way:

float sampleDepth = viewPositionFromDepth(offset.xy).z; 

This is not the most optimal way though, but it worked for me.

$\endgroup$
1
  • $\begingroup$ My issue was that my normals were in world space. I thought otherwise, but realized when you posted this. Also, You don't necessarily need 2.0 - 1.0 in the normal retrieval section if your normals are signed. $\endgroup$ Commented Jun 3, 2017 at 23:15

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.