SSAO in LibGDX sans Deferred Rendering?

I think the reason why you’re getting wrong results is because you do the matrix multiplication the wrong way around. Remember that matAmatB != matBmatA. However, I’ve been thinking about this, and I think it’s possible to simplify this.

What we really want to do is rotated the samples around the Z-axis. If we look at the raw sample offsets, this just means rotating the XY coordinates separately, leaving the Z intact. Such a rotation matrix should be much easier to construct:


	float angle = rand(texCoords) * PI2;
	float s = sin(angle);
	float c = cos(angle);
	mat3 rotation = mat3(
		c, -s, 0,
		s,  c, 0,
		0, 0, 1
	);
	//We want to do kernelMatrix * (rotation * samplePosition) = (kernelMatrix * rotation) * samplePosition
	mat3 finalRotation = kernelMatrix * rotation;

This should be faster and easier to get right!

The current JOML snapshot version 1.9.5-SNAPSHOT (latest version on GitHub) contains the possibility to generate best-candidate samples on the unit hemisphere around the +Z axis with Z in [0…+1]. The API changed a bit, too, towards a more “builder-like” pattern:


float[] samples = new float[numSamples * 3];
new BestCandidateSampling.Sphere()
  .seed(seed)
  .numSamples(numSamples)
  .numCandidates(numCandidates)
  .onHemisphere(true)
  .generate(samples);

Alright! I got the best candidate samples to work. I need a little more info theagentd:

The rotation matrix is calculating correctly, but I’m curious what I’m supposed to exchange the random vector with now the noise texture is gone? I set it to 0,1,0 to test in my post from yesterday, and I hoped you’d pick up on it… Here’s a sample.

float angle = rand(v_texCoords) * 6.28318;
float s = sin(angle);
float c = cos(angle);
mat3 rotation = mat3(
  c, -s, 0,
  s,  c, 0,
  0, 0, 1
);

vec3 randomVector = vec3(0, 1, 0);
vec3 tangentView = normalize(randomVector - dot(randomVector, normalView) * normalView);
vec3 bitangentView = cross(normalView, tangentView);
mat3 kernelMatrix = mat3(tangentView, bitangentView, normalView) * rotation;

Sorry about the bump, but the only way I get any output past full 1.0 is when I remove the rotation matrix. Take a look at this.

This is using the bestcandidatesampling, and here’s the output of that.

[-0.7227158, -0.6058225, 0.33265734,
 0.7488476, 0.6624017, 0.021243215,
 -0.61065674, 0.7918451, 0.008934975, 
0.57487714, -0.49303505, 0.65301824,
 0.03831022, 0.5855623, 0.80972165,
 0.9759306, -0.21806261, 0.0028834343,
 -0.72111094, 0.086945206, 0.6873424, 
-0.0080795605, -0.99995875, 0.0041467547,
 -0.13685125, -0.649704, 0.7477677, 
0.67209387, 0.35918146, 0.6475172,
 -0.97659266, 0.21375246, 0.024014235,
 0.087515585, 0.9748422, 0.20499706,
 -0.5405089, 0.6260205, 0.5620929, 
0.15146911, -0.13660306, 0.97897744,
 0.6733053, -0.7337369, 0.09105086,
 0.90603316, -0.025514243, 0.42243695,
 0.5041927, 0.7524756, 0.42375714,
 -0.23089148, 0.08173944, 0.96954,
 -0.9509538, -0.29908586, 0.07895911,
 0.32695338, -0.8306433, 0.45070308,
 -0.11253592, 0.8298539, 0.54651463,
 0.5440647, -0.0063848053, 0.83901894,
 -0.8152317, 0.3753412, 0.44104004, 
0.9406799, 0.3066772, 0.14515686,
 -0.3857175, -0.8732388, 0.29778516,
 -0.5603924, -0.29158565, 0.77520204,
 0.220142, -0.57200706, 0.7901553,
 0.33273157, 0.2870272, 0.89827895,
 0.8156582, -0.46193165, 0.34831142,
 -0.9124656, -0.0972097, 0.3974378,
 -0.8566877, 0.49673495, 0.13907069,
 -0.4933554, -0.65016884, 0.57782435]

This may be incorrect, I modified the code very slightly to work with LibGDX’s vectors (but the functions are identical…)

Any help on my previous post + suggestions would help!!!

I’ve been super busy, sorry.

I didn’t realize the random vectors essentially filled the same purpose as the random rotations. You can drop the rotation matrix I gave you and just use the random vector texture you had. Please post how you sample from it. I recommend a simple texelFetch() with the coordinates &-ed to keep them in range.

I’ve reverted to the old code! Here’s a screenshot of the current results, and the code that comes with it

Here’s the raw output of the random texture:

Here’s how I sample it:

// Setting the uniform
setParamsv(Param.RotationNoiseScale, new float[]{(float) Gdx.graphics.getWidth()/4f, (float)Gdx.graphics.getHeight()/4f}, 0, 2);
// Sampling
normalize(texture(u_texture3, v_texCoords * u_rotationNoiseScale).xyz * 2.0 - 1.0);

Here’s the entire shader:
http://pastebin.java-gaming.org/7c4de38495c1c

I’m almost there!!! Turns out I had a combination of problems due to calculating view normals and random texture input to the GLSL shader. One last problem:

Any idea how to make this fade more? Thanks!

if [icode]occlusion[/icode] is [icode]0.0 … 1.0[/icode] then [icode]occlusion *= occlusion[/icode] will fade more.

Since you are already using samples on a normal-oriented hemisphere, you should also use cosine-weighted occlusion factors.
Currently, the contribution of light along every direction is the same so that occlusion is just a linear function of how many AO samples indicated occlusion.
But in the real world a surface receives more light (the surface’s irradiance) from light sources directly facing the surface and less light from sources at an angle to the surface. Since with AO we are assuming that light is equally coming in from everywhere, when some light is blocked along the surface’s normal then the surface will receive a lot less light than when light was instead blocked from directions at an angle to the normal, since that light would also not have contributed much to the irradiance of the surface. And the factor by which light at an angle contributes less to the irradiance than light directly facing the surface (i.e. along its normal) is exactly [icode]cos(angleOfLightDirectionToNormal)[/icode] which can be computed via the dot product when you have both the normal vector and the light direction vector, which in our case of AO is the sample direction vector.
So, you do not just [icode]occlusion += 1.0[/icode] but instead [icode]occlusion += weightedOcclusionFactor[/icode].
For that to work you must make sure that when all AO samples would indicate occlusion, then the sum of all weighted occlusions must add up to 1.0 in total. This can be done by simply computing the cosine weight factors of all AO sample directions beforehand, summing them all up and then divide each factor by that sum to normalize them to make their sum be 1.0.