I have a strange issue in my engine where my SSAO effect will extremely darken or lighten based on my camera angle:

SSAO Issue

In that example, I'm just outputting the ssao texture to the screen. The texture is stored as a single (red) 8 bit channel.

I've been following a few tutorials here and there for building my SSAO shader, primarily this and that, so my code is pretty much identical.

I've done some research beforehand, however I couldn't really find anything that matched my error.

The only thing I found was that some people who were doing SSAO do this thing where they offsetted their scene's normals before saving it:

normalTexture = normal * 2.0f - 1.0f;

And then when needing to access the scene's normals, they revert the offset such as:

vec3 normal = texture2D(normalTexture) * 0.5f + 0.5f;

This type of thing I can almost understand, but I've never come across this is any tutorial that I've been following for any of the shading I've been doing up to this point. Further, the normals are normalized so much anyways. Regardless, if I do this or not, surfaces running parallel to different axes do the exact same thing anyways in my SSAO shader.

During my geometry pass, where I fill my GBuffer with the current frame's textures, this is how I calculate the normals:

layout (location = 2) out vec3 NormalOut; 

void main()
    vec3 Normal        = normalize(Normal0);  // The geometry's physical normal, in world space
    vec3 Tangent       = normalize(Tangent0); // The geometry's physical tangent, in world space
    Tangent            = (Tangent - dot(Tangent, Normal) * Normal);

    vec3 Bitangent     = cross(Tangent, Normal);
    vec3 BumpMapNormal = texture(NormalTexture, TexCoord0.xy).xyz; // The bumpmap texture for the skin of the object
    BumpMapNormal      = (BumpMapNormal * 2.0 - 1.0);    
    mat3 TBN           = mat3(Tangent, Bitangent, Normal);
    vec3 NormalFinal   = normalize((TBN * BumpMapNormal));
    NormalOut          = normalize(0.5 * NormalFinal + 0.5); // Final normal

My SSAO fragment shader:

in vec2 TexCoord;
layout (location = 0) out float FragColor; 

uniform sampler2D ViewPosMap;
uniform sampler2D NormalMap;
uniform sampler2D NoiseMap;

const int MAX_KERNEL_SIZE = 128;
uniform vec3 Kernel[MAX_KERNEL_SIZE];

uniform vec2 NoiseScale;
uniform float SampleRadius;
uniform mat4 pMatrix;

void main()
    vec3 Pos = texture(ViewPosMap, TexCoord).xyz;
    vec3 Normal = texture(NormalMap, TexCoord).xyz * 2.0 - 1.0 ;
    vec3 RandomVec = texture(NoiseMap, TexCoord * NoiseScale).xyz;          
    vec3 Tangent = normalize(RandomVec - Normal * dot(RandomVec, Normal));
    vec3 Bitangent = cross(Normal, Tangent);
    mat3 TBN = mat3(Tangent, Bitangent, Normal);  

    float occlusion = 0.0;
    for (int i = 0 ; i < USABLE_KERNEL_SIZE ; i++) {
        vec3 sample = TBN * Kernel[i]; // From tangent to view-space
        sample = Pos + sample * SampleRadius; 

        // project sample position (to sample texture) (to get position on screen/texture)
        vec4 offset = vec4(sample, 1.0);
        offset = pMatrix * 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

        // get sample depth
        float sampleDepth = texture(ViewPosMap, offset.xy).z; // Get depth value of kernel sample

        // range check & accumulate
        float rangeCheck = smoothstep(0.0, 1.0, SampleRadius / abs(Pos.z - sampleDepth));
        occlusion += (sampleDepth >= sample.z ? 1.0 : 0.0) * rangeCheck;              

    occlusion =  1.0 - (occlusion / USABLE_KERNEL_SIZE);
    FragColor = occlusion;

And here is a sample of what the normals look like in the scene: SSAO Issue

I feel like I'm banging my head against the wall here, I'm not quite sure why I'm getting the results that I am. If anyone wants any further information, I'll post it as soon as I can.

The solution provided in this thread was exactly what I needed. I created an additional GBuffer texture, one which held the scene's normals but in viewspace. I then sent this texture to my ssao shader, and it corrected the issue I was having.

  • 1
    \$\begingroup\$ Not done looking through it yet. But I noticed a few things. You probably want to normalize your tangent in the geometry shader after making it perpendicular to the normal. Otherwise your tangent and bitangent might now have length 1 at the end. Then in your SSAO shader the comment says you are transforming from tangent to view space. But you are adding it to something sampled from ViewPosMap which seems to be in screen space. \$\endgroup\$ Commented Jan 23, 2016 at 13:10

1 Answer 1


To me it looks very much like your issue here is that you are mixing world and viewspace or something similiar.

Now your GBuffer normals look like they might be in viewspace in that picture, but the code in your geometry pass definitely doesn't transform them from world to view space.

If they are in world space then your NBT matrix is basically oriented incorrectly by exactly the rotation of your view matrix, so your kernel will end up being rotated into or out of the wall depending on where you are looking. And that would give you exactly the result you are seeing.

Hope this helps.

(Minor sidenote: you are only interested in offset.xy, so after projecting you can just use the two rather than offset.xyz)

  • \$\begingroup\$ I've heard a lot of bad things about transforming normals to view space, would it be sufficient to do that in this case, or should I transform my sample back into world space? \$\endgroup\$
    – Yattabyte
    Commented Jan 23, 2016 at 15:22
  • \$\begingroup\$ Either will work, it's just important that you are consistent. \$\endgroup\$ Commented Jan 23, 2016 at 15:33
  • \$\begingroup\$ Alright, this seems to have vastly improved the results I had. I am still a bit curious though - should I be offsetting the normals before storing and reading them (the normal * 2 - 1 and normal * 0.5 + 0.5)? I don't seem to see any difference on my end right now between doing it and not doing it. \$\endgroup\$
    – Yattabyte
    Commented Jan 23, 2016 at 16:52
  • 1
    \$\begingroup\$ It's not so much offsetting them as mapping them from the [-1,1] into the [0,1] range. Because if you store it in a non-floating point texture, it will get mapped to colors between 0 and 1, so all the negative values would have been turned into 0. In order to avoid that you tend to map the normals into the 0 to 1 range. But that depends on your render target. \$\endgroup\$ Commented Jan 23, 2016 at 16:56
  • \$\begingroup\$ Oh I see, so the texture being set to GL_FLOAT circumvents that problem from occurring. Then that explains why I didn't really notice any differences. \$\endgroup\$
    – Yattabyte
    Commented Jan 23, 2016 at 16:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .