SSAO Artifacts

Hey guys, it’s me - again …

I’ve some issues porting the SSAO implementation from XNA to mono. As you will see in the screenshot below, a weird banding does occur. Also there are some strange lines inside the image which will flicker as the camera moves.

The random normals for the SSAO effect are taken from a funk-tastic random normal texture - so there should be no banding?

Any suggestions about that? It might be the testing scene I am using, but I can not remember having such issues in the old implementation …

The FAR plane is set to 1000.0f.

My depth map is set to “Single”. SSAO params are::
bias = 0.5f;
intensity = 1.5f;
sampleRadius = 0.5f;
scale = 1.5f;

SSAO-HLSL::

float4x4 param_inverseViewProjectionMatrix;
float4x4 param_inverseViewMatrix;
float3 param_frustumCornersVS[4];
float param_randomSize;
float param_sampleRadius;
float param_intensity;
float param_scale;
float param_bias;
float2 param_screenSize;

texture param_normalMap;
texture param_depthMap;
texture param_randomMap;



sampler normalSampler = sampler_state
{
	Texture = (param_normalMap);
	AddressU = CLAMP;
	AddressV = CLAMP;
	MagFilter = POINT;
	MinFilter = POINT;
	Mipfilter = NONE;
};
sampler depthSampler = sampler_state
{
	Texture = (param_depthMap);
	AddressU = CLAMP;
	AddressV = CLAMP;
	MagFilter = POINT;
	MinFilter = POINT;
	MipFilter = NONE;
};
sampler randomSampler = sampler_state
{
	Texture = (param_randomMap);
	AddressU = WRAP;
	AddressV = WRAP;
	MagFilter = POINT;
	MinFilter = POINT;
	MipFilter = NONE;
};



// Define VS input
struct VSIn
{
	float3 Position	: POSITION0;
	float3 TexCoord : TEXCOORD0;
};

// Define VS output and therefor PS input
struct VSOut
{
	float4 Position : POSITION0;
	float2 TexCoord: TEXCOORD0;
	float3 FrustumCornerVS : TEXCOORD1;
};

// Define PS output
struct PSOut
{
	float4 Color : COLOR0;
};



// Reconstruct view-space position from the depth buffer
float3 getPosition(in float2 vTexCoord, in float3 in_vFrustumCornerVS)
{
    float fPixelDepth = tex2D(depthSampler, vTexCoord).r;
    return float3(fPixelDepth * in_vFrustumCornerVS);
}

// Calculate the occlusion term
float doAmbientOcclusion(in float2 tcoord, in float3 p, in float3 cnorm, in float3 in_vFrustumCornerVS)
{
	float3 diff = getPosition(tcoord, in_vFrustumCornerVS) - p;
	float3 v = normalize(diff);
	float d = length(diff) * param_scale;

	return max(0.0, dot(cnorm, v) - param_bias) * (1.0 / (1.0 + d)) * param_intensity;
}



/**************************************************
Vertex shader.
**************************************************/
VSOut MainVS(VSIn input)
{
	VSOut output;

	output.Position = float4(input.Position, 1);
	output.TexCoord = input.TexCoord.xy;
	output.FrustumCornerVS = param_frustumCornersVS[input.TexCoord.z];

	return output;
}



/**************************************************
Pixel shader.
**************************************************/
PSOut MainPS(VSOut input)
{
	PSOut output;

	const float2 vec[4] = {
		float2(1,0),
		float2(-1,0),
		float2(0,1),
		float2(0,-1)
	};

	float3 p = getPosition(input.TexCoord, input.FrustumCornerVS);
	float3 n = normalize(tex2D(normalSampler, input.TexCoord).xyz * 2.0f - 1.0f);
	float2 rand = normalize(tex2D(randomSampler, param_screenSize * input.TexCoord / param_randomSize).xy * 2.0f - 1.0f);

	float ao = 0.0f;
	float rad = param_sampleRadius / p.z;

	int numIterations = 4;
	for (int j = 0; j < numIterations; ++j) {
		float2 coord1 = reflect(vec[j], rand) * rad;
		float2 coord2 = float2(coord1.x - coord1.y, coord1.x + coord1.y) * 0.707f;

		ao += doAmbientOcclusion(input.TexCoord + coord1 * 0.25, p, n, input.FrustumCornerVS);
		ao += doAmbientOcclusion(input.TexCoord + coord2 * 0.50, p, n, input.FrustumCornerVS);
		ao += doAmbientOcclusion(input.TexCoord + coord1 * 0.75, p, n, input.FrustumCornerVS);
		ao += doAmbientOcclusion(input.TexCoord + coord2 * 1.00, p, n, input.FrustumCornerVS);
	}

	ao /= (float)numIterations * 4.0;
	ao = saturate(ao * param_intensity);

	output.Color = 1 - ao;

	return output;
}



technique Default
{
	pass SSAO
	{
		VertexShader = compile vs_4_0 MainVS();
		PixelShader = compile ps_4_0 MainPS();
	}	
}

BTW. The ambient light is cranked up to a ridiculous value for this image :wink:

Hi !

What happens when p.z is 0 in float rad = param_sampleRadius / p.z; ? But I don’t think it is the source of the problem.

Sorry, but can you clarify what p.z. is standing for ?

It is your code, not mine :wink:

But I suppose it is the depth at the current pixel.
When dividing by 0 (close to the camera), what happens.

Is the ground plane formed of a high number of triangles ?

Brrrrrrrr…

p.z => Position.z??
Like in “float3 p = getPosition(input.TexCoord, input.FrustumCornerVS);” ?

Sorry. The shader code is taken from (I think) MJP or Calatin Zima some 5 years ago.

The ground plane is a simple plane using 2 triangles.

Just step through your shader step by step and see if the output matches the expectation (for example the random vectors, the reflected vectors etc.)

Try to not use random texture and see if the problems are still there. Try to compute normals with ddx, ddy instead of the map etc

Just a little update on this issue since I am currently bound in a critical project in my fulltime job …

@Alkher: The problem magnifies with smaler “rad” values. So near the camera (small depth) it disappears.

@kosmonautgames: Still on it :slight_smile: Position is reconstructed correctly and the normals also look fine. It all boils down to an issue with “coord1” in the PS. The banding still occurs, but it seems like there is something funky going on with the random normals/ reflection.

Do you take into account the texel size ?
A good tutorial where some things are explained:

Necro time, that tutorial from Chapman is one of the most beautiful pit traps I ever seen (in context of GLSL → HLSL in this particular case), I wonder how OP liked it. Anyway, if someone will be implementing it in MG then expect to flip pretty much every viewport related z and setup debugging output, make sure that everything is in correct space, if anyone wants MG ready version let me know, I personally don’t plan to use it, kosmonaut’s version from his deferred playground is imho superior.