Strided blur and other tips for SSAO
If you’re new to SSAO, here are good overview blog posts: meshula.net and levelofdetail. Some tips and an idea on strided blur below.
Bits and pieces I found useful
- SSAO can be generated at a smaller resolution than screen, with depth+normals aware upsample/blur step.
- If random offset vector points away from surface normal, flip it. This makes random vectors be in the upper hemisphere, which reduces false occlusion on flat surfaces. Of course this requires having surface normals.
- When generating random vectors for your AO kernel:
- Generate vectors inside unit sphere (not on unit sphere).
- Use energy minimization to distribute your samples better, especially at low sample counts. See malmer.ru blog post.
- In your AO blurring/upsampling step: no need to sample each pixel for blur. Just skip some of them, i.e. make kernel offsets larger. See below.
Strided blur for AO
Normally you’d blur AO term using some sort of standard blur, for example separable Gaussian: horizontal blur, followed by vertical blur. How one can imagine horizontal blur kernel:

Here’s how Rune taught me how to blur better:
- Rune:
- The other thing is the blur. I tried to make the blur 4 times stronger, and it looks much better IMO without any artifacts I could see. I could even use 4x downsampling with that blur amount and still get acceptable results.
- Aras:
- how did you make it 4x stronger? (I was going to say that blur step is already quite expensive, and I don’t want to add more samples to make it even more expensive, yadda yadda)
- Rune:
- m_SSAOMaterial.SetVector (“_TexelOffsetScale”, m_IsOpenGL ?
new Vector4 (4,0,1.0f/m_Downsampling,0) :
new Vector4 (4.0f/source.width,0,0,0));
And similar for vertical.- Aras:
- hmm. that’s strange :)
- Rune:
- I have no idea what I’m doing of course but it looks good.
- Aras:
- so this way it does not do Gaussian on 9×9 pixels, but instead only takes each 4th pixel. Wider area, but… it should not work! :)
- Rune:
- It creates a very fine pattern at pixel level but it’s way more subtle than the noise you get otherwise.
- Aras:
- ok (hides in the corner and weeps)
So yeah. The blur kernel can be “spread” to skip some pixels, effectively resulting in a larger blur radius for the same sample count:

Yes, it’s not correct blur. But that’s okay, we’re not building nuclear reactors that depend on SSAO blur being accurate. If you are, SSAO is probably a wrong approach anyway, I’ve heard it’s not that useful for nuclear stuff.
I’m not sure how this blur should be called. Strided blur? Interleaved blur? Interlaced blur? Or maybe everyone is doing that already and it has a well established name? Let me know.
Some images of blur in action. Raw AO term (very low – 8 – sample count and increased contrast on purpose):

Regular 9×9 blur (does not blur over depth+normals discontinuities):

Blur that goes in 2 pixel stride (effectively 17×17):

It does create a fine interleaved pattern because it skips pixels. But you get wider blur!

Blur that goes in 3 pixel stride (effectively 25×25):

At 3 pixel stride the artifacts are becoming apparent. But hey, this is very
low AO sample count, increased contrast and no textures in the scene.

For sake of completeness, the same raw AO term, but computed at 2×2 smaller resolution (still using low sample count etc.):

Now, 2×2 smaller AO, blurred with 3 pixels stride:

Happy blurring!

So that’s bilateral blur you’re using, not gaussian, right? Otherwise how do you get it to respect the boundaries?
@imbusy: yeah, something like that. I’m not sure if it’s exactly “bilateral”, but basically it discards samples based on depth/normal difference.
It shouldn’t be separable, but hey, you said it yourself, we’re not building nuclear reactors :)
I discovered something similar when doing PCSS on a project last year – that although I should have needed to increase the number of PCF samples as the kernel widened, it was surprising what you could actually get away with in practice. And interestingly, the more detailed / non-uniform your diffuse textures are, the more you can get away with, because the diffuse channel makes it even harder to spot the ‘dithering’ patterns.
Moral of the story, theory sucks, random experimentation FTW :)
@Aras: I can’t believe you didn’t knew this “trick”… I’m pretty sure we’ve been doing it in late Interamotion days even.
[...] This is one more reason the Internet is great: an in-depth article on normal compression techniques, weighing the pros and cons of each. This sort of article would probably not see the light of day in traditional publications, even Game Developer – too long for them, but all the info presented here is worthwhile for a developer making this decision. Aras’ blog has other nice bits such as packing a float into RGBA and SSAO blurring. [...]
@imbusy: well yeah, it’s not bilateral in fact. I’m just doing kinda-Gaussian, and discarding samples that don’t meet some criteria.
@steve: word!
@ReJ: I don’t remember us using it, actually. Or I have already forgot about it :)
Using bilateral or just discarding – it’s still not separable.
Something that I’ve suggested a few times, but never tried myself, is to use some sort of importance sampling. Pixels closer to the center should be sampled at a higher frequency, since they have more important contributions, than pixels with lower contributions. Note that you would also have to readjust the weights. This is basically like the 2D poison disk distribution that ATI used in some of the old demos, but on one dimension only.
@imbusy: ok ok. But it works! :)
@icastano: yeah, makes sense.
Would be interesting to try adjusting filter tap location by a function of VPOS to attempt to break up the interleave pattern (assuming TEX bound with free ALU capacity). Might play havoc on texture cache however.
Might also be interesting to play with temporal feedback in the filtering (like Gears II) but in combination with Ignacio’s suggestion of importance sampling and changing filter position temporally.