opengl-esthree.jsglslssao

SSAO artefacts in Three


I'm really struggling to fix an issue with my SSAO shader and could desperately use some help. Basically the shader seems to work on some objects, but looks really bad on others. From the below you can see the sphere looks correct, but the cube seems to be doing occlusion on normals that it shouldn't. Here is a screenshot:

Notice edges of cube

I am basing my shaders off of this tutorial: http://devmaster.net/posts/3095/shader-effects-screen-space-ambient-occlusion

In my render chain I render 2 render targets which get used in some post process effects later on. One of them stores the position and depth. The other stores the normals. Both targets are float textures.

Here is a screenshot of each of the steps.

Normals, Depth, Position

This is my position/depth shader:

Vertex shader:

varying vec4 vPosition;
.....
vPosition = mvPosition;

Frag Shader:

uniform float linearDepth; //Cam far - cam near
varying vec4 vPosition;
...
float ld = length(vPosition) / linearDepth;
gl_FragColor = vec4(vPosition.x, vPosition.y, vPosition.z, ld);

This is the normal shader:

Vertex shader:

varying vec3 vNormal;
...
vec3 transformedNormal = normalMatrix * objectNormal;
vNormal = transformedNormal;

Fragment shader:

gl_FragColor = vec4( normalize( vNormal ).xyz, 1.0);

Here is my SSAO fragment shader:

uniform sampler2D tDiffuse;    // The original scene texture
uniform sampler2D tPositions;    // View space position data
uniform sampler2D tNormals;    // View space normal vectors
uniform sampler2D tNoise;    // Normalmap to randomize the sampling kernel
uniform vec2 texelSize;

/// Occluder bias to minimize self-occlusion.
uniform float occluderBias;

/// Specifies the size of the sampling radius.
uniform float samplingRadius;

uniform float onlyAO;

/// <summary>
/// Ambient occlusion attenuation values.
/// These parameters control the amount of AO calculated based on distance
/// to the occluders. You need to play with them to find the right balance.
///
/// .x = constant attenuation. This is useful for removing self occlusion. When
///         set to zero or a low value, you will start to notice edges or wireframes
///         being shown. Typically use a value between 1.0 and 3.0.
///
///    .y = linear attenuation. This provides a linear distance falloff.
/// .z = quadratic attenuation. Smoother falloff, but is not used in this shader.
/// <summary>
uniform vec2 attenuation;


/// <summary>
/// Varying variables.
/// <summary>
varying vec2 vUv;

/// <summary>
/// Sample the ambient occlusion at the following UV coordinate.
/// <summary>
/// <param name=srcPosition>3D position of the source pixel being tested.</param>
/// <param name=srcNormal>Normal of the source pixel being tested.</param>
/// <param name=uv>UV coordinate to sample/test for ambient occlusion.</param>
/// <returns>Ambient occlusion amount.</returns>
float SamplePixels (vec3 srcPosition, vec3 srcNormal, vec2 uv)
{
    // Get the 3D position of the destination pixel
    vec3 dstPosition = texture2D(tPositions, uv).xyz;

    // Calculate ambient occlusion amount between these two points
    // It is simular to diffuse lighting. Objects directly above the fragment cast
    // the hardest shadow and objects closer to the horizon have minimal effect.
    vec3 positionVec = dstPosition - srcPosition;
    float intensity = max(dot(normalize(positionVec), srcNormal) - occluderBias, 0.0);

    // Attenuate the occlusion, similar to how you attenuate a light source.
    // The further the distance between points, the less effect AO has on the fragment.
    float dist = length(positionVec);
    float attenuation = 1.0 / (attenuation.x + (attenuation.y * dist));

    return intensity * attenuation;
}


/// <summary>
/// Fragment shader entry.
/// <summary>
void main ()
{
    // Get position and normal vector for this fragment
    vec3 srcPosition = texture2D(tPositions, vUv).xyz;
    vec3 srcNormal = texture2D(tNormals, vUv).xyz;
    vec2 randVec = normalize(texture2D(tNoise, vUv).xy * 2.0 - 1.0);
    float srcDepth = texture2D(tPositions, vUv).w;

    // The following variable specifies how many pixels we skip over after each
    // iteration in the ambient occlusion loop. We can't sample every pixel within
    // the sphere of influence because that's too slow. We only need to sample
    // some random pixels nearby to apprxomate the solution.
    //
    // Pixels far off in the distance will not sample as many pixels as those close up.
    float kernelRadius = samplingRadius * (1.0 - srcDepth);

    // Sample neighbouring pixels
    vec2 kernel[4];
    kernel[0] = vec2(0.0, 1.0);        // top
    kernel[1] = vec2(1.0, 0.0);        // right
    kernel[2] = vec2(0.0, -1.0);    // bottom
    kernel[3] = vec2(-1.0, 0.0);    // left

    const float Sin45 = 0.707107;    // 45 degrees = sin(PI / 4)

    // Sample from 16 pixels, which should be enough to appromixate a result. You can
    // sample from more pixels, but it comes at the cost of performance.
    float occlusion = 0.0;
    for (int i = 0; i < 4; ++i)
    {
        vec2 k1 = reflect(kernel[i], randVec);
        vec2 k2 = vec2(k1.x * Sin45 - k1.y * Sin45,
                       k1.x * Sin45 + k1.y * Sin45);
        k1 *= texelSize;
        k2 *= texelSize;

        occlusion += SamplePixels(srcPosition, srcNormal, vUv + k1 * kernelRadius);
        occlusion += SamplePixels(srcPosition, srcNormal, vUv + k2 * kernelRadius * 0.75);
        occlusion += SamplePixels(srcPosition, srcNormal, vUv + k1 * kernelRadius * 0.5);
        occlusion += SamplePixels(srcPosition, srcNormal, vUv + k2 * kernelRadius * 0.25);
    }

    // Average and clamp ambient occlusion
    occlusion /= 16.0;
    occlusion = clamp(occlusion, 0.0, 1.0);

// Blend the two 
vec3 colour =  texture2D(tDiffuse, vUv).xyz;
//colour = clamp(colour - occlusion, 0.0, 1.0);

occlusion = 1.0 - occlusion;

//gl_FragColor.xyz = pow(colour, vec3(1.0 / 2.2));
if ( onlyAO == 1.0 )
    gl_FragColor.xyz = vec3( occlusion, occlusion, occlusion );
else if ( onlyAO == 2.0 )
    gl_FragColor.xyz = vec3( srcNormal.x, srcNormal.y, srcNormal.z );
else if ( onlyAO == 3.0 )
    gl_FragColor.xyz = vec3( srcDepth, srcDepth, srcDepth );
else
{
    //// Blend the two 
    //colour = clamp(colour - occlusion, 0.0, 1.0); 

    //// Apply gamma correction 
    //gl_FragColor.xyz = pow(colour, vec3(1.0 / 2.2)); 
    //gl_FragColor.w = 1.0;

    gl_FragColor.xyz = colour * occlusion;
}

gl_FragColor.w = 1.0;

}

Solution

  • It looks like normals aren't perpendicular to the surface of the cube, that might be the issue coming from shading param of the material.

    Try setting THREE.FlatShading param for shading option for material.