superfast attenuation that is
easy to work with and
helps our hybrid lighting

lighting #2 – attenuation


by Nicholas Francis
Jun 17, 2013

This is the part of a series of blog posts on rendering in our cyberpunk tablet game, Static Sky. For an introduction, check out our overview of rendering post.

In this post, we’re going to look at how light attenuates, both in traditional realtime rendering and with our modified falloffs for Static Sky.

The attenuation of light in games traditionally matches how light behaves in the physical world; it fades out quickly at first, but has a tiny effect for a very long range. While this might be physically correct, it has some issues that occur when ideal workflow and performance are at odds. It’s easiest to explain when looking at a picture:

In this picture, we are trying to achieve soft illumination coming from a self-lit billboard. You can see that in order achieve that goal, we’ve had to put a very large range on the light.

This wouldn’t be a problem with a single light, but since we’re usually limited by how many realtime lights can effect each object, filling the scene with tons of huge point lights that contribute relatively little is far from ideal.

Since we’d like to have more lights in our scene, we do something like this — reduce the range so that it doesn’t illuminate so many things and increase the brightness to compensate.

This strategy works just fine — until we begin playing the game and something passes close by.

Ouch — the light is so intense that objects that move close to it get way too bright.

It’s really hard to achieve nice, soft lighting using only point lights. Our options are to accept something that is too big or something that is too bright. We could bake some area lights into lightmaps, but losing speedy iteration and realtime changes is unacceptable.

the maths

In the traditional shading models, the light attenuation is defined as (more or less) \( \frac{1}{d^2 + 1}\). This is physically correct but has several issues. Let’s graph it out:

Falloff

  • It takes forever to reach 0. While physically correct, it makes culling a real pain. In Unity, they just decide that light gets culled at 1/256
  • Most of the light is ‘centered’ around the light source (x < 1 above). This means you need very large lights.
  • We saw in the previous blog post how hybrid lighting doesn’t like overlapping lights, so a more ‘focused’ attenuation curve is a lot nicer
  • (doesn’t really concern us, but if you’re using deferred lighting, there’s a lot of pixels getting processed for very little gain)

We don’t want to calculate more than we absolutely have to. Also, we saw in the first post that our hybrid lights perform best when there’s few lights affecting each vertex. This means we’d like a falloff that’s a bit more concentrated. Fortunately, nearly every light attenuation algorithm that can be tried has been tried, and we didn’t have to look very far (Just Cause 2) to find a published formula that works better for our needs: $$ atten = 1 – d^2 * (\frac {1}{range^2})^2 $$

Falloff2
The shape is similar to the one above, but with a few advantages:

  • Light is more evenly distributed, which removes the need for huge ranges or high intensities.
  • As X approaches one, lighting goes to zero. This is super nice as we know that the light stops at the full range of the light source.
  • The light is clearly visible in the area we’re calculating it.
Taken as a whole, this attenuation model is easier to work with and will result in more performant scenes.

the code

Let’s see how we can code this. Outside the vertex program, we can calculate \(\frac {1}{range^2} \) for each light. Inside the vertex program, we can then do:

float3 lightToVertex = lightPos - vertex;
float sqrDist = dot (lightToVertex, lightToVertex);
float atten = max (0, 1 - sqrDist * OneOverLightRangeSqr);
atten *= atten;

Since we will need lightToVertex for our \(n \cdot l\) shading calculation anyways, the cost of this attenuation function is only 2 cycles.

 

Above is the result of our substituted attenuation factor. We’ve got the short range of light, and we don’t have to make it super bright to compensate (as you can see by the floating box).

To sum up, we have an attenuation that’s superfast to calculate and minimized the weird aspects of the hybrid lighting. The falloff is somewhat unnatural — and it can end up looking a bit weird when the light gets very bright (esp. in bright specular patches), but we figured it was worth it.

That’s it for this time. For my next installment, I’ll look into how we determine what lights get rendered, and go into some of the more Unity-specific details of how we made our own lights feel just like Unity’s built-in.

Over & out
Nich


by Trideka
June 17, 2013

Nich, thanks so much for these posts.

A) They really make me want the game.

B) As a Unity user, your insights are really revealing and interesting to me. Learning so much!

Keep it up!


by Trond
July 31, 2013

Great stuff! You certainly have some nice rendering tricks up your sleeve, I love these posts 🙂 Thanks for sharing!


by Ryan Gatts
December 26, 2013

This is a problem I’ve been puzzling over for months now. You show an excellent attenuation for broad illumination — one that I intend to use as soon as an opportunity presents itself — but I’m worried about losing the more focused style of illumination for other light sources. Has this ever been an issue for you, and if so, How would you go about ‘tagging’ individual lights to be handled using different attenuations?


by Teck Lee Tan
January 29, 2014

Though having a think about it, I suppose it would be pretty trivial to have each entity keep a list of lights affecting it and update only when that list changes.

Add Your Comment