Published: October 7, 2025
8
33
215

A bit technical thread 🧵 I’ve been improving the in-game ambient occlusion system - and ended up with something I call: Blob-based Analytical Ambient Occlusion on Primitives Here’s what it does to the final image: (more examples later in the thread) (1/16)

Image in tweet by Florisium
Image in tweet by Florisium

Since the game’s main platform is mobile, I needed a fast and robust AO solution for dynamic objects (static ones are lightmapped). Why not Screen-space AO? Not great for mobile - too noisy, not tile-based GPU friendly, inconsistent across devices, and needs heavy filtering.

So, I went back in time and revived a classic: blob shadows - the technique used in early 3D games that often mimicked ambient occlusion rather than true shadows. (3/16)

Image in tweet by Florisium

The base idea is beautifully simple: 1. Define surfaces that receive occlusion. 2. Define objects that cast occlusion. 3. For each receiver–caster pair, compute the projected occlusion area that roughly estimates shadowing of the cast object. 4. Apply some shadow (occlusion)

Image in tweet by Florisium

For the first version of the system, I used static textures to approximate the occlusion shape of some typical primitives. Just 3 texture types - pulled on for every moving object in the game (could be more than 1 shape per object). It worked! Results were way better than no AO

Image in tweet by Florisium

So I finally found the courage to revisit the technique. This time - no prebaked textures. Instead, I went analytical - doing the math to emulate occlusion shapes directly for some simple primitives (boxes, spheres, cylinders, cones, etc). (6/16)

The core 2D case is actually simple: Imagine a flat occluder of width 2W (from -W to W) standing at height H. The occlusion function looks like: (cos(arctan((x - W) / H)) + cos(arctan((-x - W) / H))) / 2 + 1 In other words - the sum of light coming from the left and right gaps

Image in tweet by Florisium

Luckily, this simplifies nicely since cos(arctan(x)) = x / sqrt(x² + 1). That gives a super lightweight, computation-friendly formula that still behaves like smooth, realistic occlusion. And can be easily extended with some additional parameters. You can play with the

The 2D version is a good start - but it doesn’t capture the full picture. We need one more dimension to handle, which makes things trickier. Ideally, this should be a hemispherical integral of point visibility. In practice, I simplified it by splitting it into vertical and

The vertical part comes from the formula I showed earlier - nice and straightforward. The horizontal part, though, depends on how visibility changes with the distance to the surface and the surface’s own size. For a top-down projected cylinder, that relation is proportional to

Image in tweet by Florisium
Image in tweet by Florisium

Rectangular shapes are trickier since they have hard edges. I ended up with a slightly different, yet still cheap, function that models the visibility per rectangle size - same logic as with cylinders. (11/16)

Image in tweet by Florisium
Image in tweet by Florisium

Side views of cylindrical shapes also needed separate math (and again, the right image shows the ray-traced reference). (12/16)

Image in tweet by Florisium
Image in tweet by Florisium

Using this system, I built a small set (7 types) of analytical AO shapes that cover most in-game dynamic props. The result is much more detailed and believable than the old texture-based version. Left = old. Right = new. (13/16)

Image in tweet by Florisium
Image in tweet by Florisium

Performance-wise, it’s similar to the previous method, but no texture fetches, all math is half-based, and only ~30–40 instructions per shape type. On an iPhone 15Pro, native res, it costs about 0.02–0.2 ms, depending on scene complexity and density (all blobs are combined to a

Bonus point: the same approach also gives cheap multi-point (and areal) lights support - in forward rendering, on mobile! 🎉🎉🎉 (15/16)

Image in tweet by Florisium
Image in tweet by Florisium

There are, of course, plenty of limitations (obvious and not-so-obvious) - I’ll dive into those next time😉 And finally, here are a few fresh in-game shots (and remember - the game is still about flowers!) (16/16)

Image in tweet by Florisium
Image in tweet by Florisium
Image in tweet by Florisium
Image in tweet by Florisium

@florisium Splat-based ao

@florisium Thanks for the breakdown, really interesting way to do AO! The results look great

@jonshamir ohh, thanks a lot!

@florisium would it really be too much heavier to just use distance fields?

@LifeArtStudios Distance fields on mobile sounds already super-heavy 😅Also, they won't give that many details that I'm capturing with per-pixel math. I would probably go with the well-known capsule-based AO or screen-space' GTAO in other circumstances...

@florisium I like the effect it’s nostalgic

@florisium nice writeup. it looks great!

@florisium wooo looks amazing

@florisium very nice! @bnolan

Share this thread

Read on Twitter

View original thread

Navigate thread

1/26