## Light attenuation

### January 31, 2011

The canonical equation for point light attenuation goes something like this:

where:

d = distance between the light and the surface being shaded

kc = constant attenuation factor

kl = linear attenuation factor

kq = quadratic attenuation factor

Since I first read about light attenuation in the Red Book I’ve often wondered where this equation came from and what values should actually be used for the attenuation factors, but I could never find a satisfactory explanation. Pretty much every reference to light attenuation in both books and online simply presents some variant of this equation, along with screenshots of objects being lit by lights with different attenuation factors. If you’re lucky, there’s sometimes an accompanying bit of handwaving.

Today, I did some experimentation with my path tracer and was pleasantly surprised to find a correlation between the direct illumination from a physically based spherical area light source and the point light attenuation equation.

I set up a simple scene in which to conduct the tests: a spherical area light above a diffuse plane. By setting the light’s radius and distance above the plane to different values and then sampling the direct illumination at a point on the plane directly below the light, I built up a table of attenuation values. Here’s a plot of a some of the results; the distance on the horizontal axis is that between the plane and the light’s *surface*, not its centre.

After looking at the results from a series of tests, it became apparent that the attenuation of a spherical light can be modeled as:

where:

d = distance between the light’s surface and the point being shaded

r = the light’s radius

Expanding this out, we get:

which is the original point light attenuation equation with the following attenuation factors:

Below are a couple of renders of four lights above a plane. The first is a ground-truth render of direct illumination calculated using Monte Carlo integration:

In this second render, direct illumination is calculated analytically using the attenuation factors derived from the light radius:

The only noticeable difference between the two is that in the second image, an area of the plane to the far left is slightly too bright due to a lack of a shadowing term.

Maybe this is old news to many people, but I was pretty happy to find out that an equation that had seemed fairly arbitrary to me for so many years actually had some physical motivation behind it. I don’t really understand why this relationship is never pointed out, not even in Foley and van Dam’s venerable tome*.

Unfortunately this attenuation model is still problematic for real-time rendering, since a light’s influence is essentially unbounded. We can, however, artificially enforce a finite influence by clipping all contributions that fall below a certain threshold. Given a spherical light of radius *r* and intensity *Li*, the illumination *I* at distance *d* is:

Assuming we want to ignore all illumination that falls below some cutoff threshold *Ic*, we can solve for *d* to find the maximum distance of the light’s influence:

Biasing the calculated illumination by *-Ic* and then scaling by *1/(1-Ic)* ensures that illumination drops to zero at the furthest extent, and the maximum illumination is unchanged.

Here’s the result of applying these changes with a cutoff threshold of 0.001; in the second image, areas which receive no illumination are highlighted in red:

And here’s a cutoff threshold of 0.005; if you compare to the version with no cutoff, you’ll see that the illumination is now noticeably darker:

Just to round things off, here’s a GLSL snippet for calculating the approximate direct illumination from a spherical light source. Soft shadows are left as an exercise for the reader.

😉

vec3 DirectIllumination(vec3 P, vec3 N, vec3 lightCentre, float lightRadius, vec3 lightColour, float cutoff) { // calculate normalized light vector and distance to sphere light surface float r = lightRadius; vec3 L = lightCentre - P; float distance = length(L); float d = max(distance - r, 0); L /= distance; // calculate basic attenuation float denom = d/r + 1; float attenuation = 1 / (denom*denom); // scale and bias attenuation such that: // attenuation == 0 at extent of max influence // attenuation == 1 when d == 0 attenuation = (attenuation - cutoff) / (1 - cutoff); attenuation = max(attenuation, 0); float dot = max(dot(L, N), 0); return lightColour * dot * attenuation; }

* I always felt a little sorry for Feiner and Hughes.

January 31, 2011 at 3:05 pm

I remember first learning about this in my electro magnetism class.

You can also prove analytically that a infinitely small point light has a falloff of d^2 and a infinite plane will have a 1/d falloff.

-= Dave

February 4, 2011 at 6:07 pm

Not quite. A point light has a falloff of 1/d^2, an infinite linear-source (an approximation of an infinite fluorescent tube) has a falloff of 1/d, and an infinite planar-source will have a falloff of 1/1.

To be clear, the term attenuation usually applies to the absorption and scattering of photons as they pass through some medium, and is often exponential based on the length of the path through that medium. The decrease in intensity that you’re describing here is purely a geometric factor due to the constant flux of a light source propagating as a spherical wavefront.

I haven’t worked through the maths to figure out why f_att isn’t a pure inverse quadratic which is what I would expect.

February 4, 2011 at 7:30 pm

Hey Dom, it’s good to hear from you. In times like this I miss being able to pick your physics brain🙂

If you’re interested in the proof, Miles pointed out over email that Iñigo Quilez worked through something very similar for analytic sphere occlusion:

http://www.iquilezles.org/www/articles/sphereao/sphereao.htm

I got lost somewhere around the forth double integral.

February 10, 2011 at 3:12 pm

Yes, sorry infinite rod is 1/d. It’s been a while.

I do also remember when I first started studying this I was talking to my friend who has his PhD in physics and asking him to explain to me the attenuation equation. We had quite the confusing conversation because in my mind attenuation was the spreading of flux and he thought of it as absorption in participating media. It would be interesting to find the origin of the term attenuation for graphics to see the authors reasoning.

February 11, 2011 at 11:22 am

Dominic is correct when he describes the usual meaning of attenuation, but correctly calculating scattering and absorption is non-trivial. In games we normally just place our world in a vacuum and hope nobody suffocates.

The effects of participating media can be approximated using a simple exponential (or even linear!) fog model, or a scattering model such as Preetham’s or Bruneton & Neyrett’s for outdoor scenes.

Glows around light sources can be approximated with a post process bloom, a simple view-aligned sprite, or a combination of both.

Unfortunately, these approaches don’t capture effects such as the softening of specular lobes due to scattering.

You have just reminded me of an interesting paper from SIGGRAPH 2005 though, which does approximate these effects for the single-scattering case.

July 7, 2011 at 9:15 am

[…] We further looked into these constants and found a relative equation which calculates the lights attenuation. This has helped us to better the brightness of our light in all four rooms. This entry was […]

February 12, 2013 at 12:18 pm

Thanks for this article. Super helpful.

Just thought I’d mention that I used this model, although I changed the definition of d from being light surface to point to light center to point. The reason being that to build natural looking lighting, artists were using large radii with moderate intensity, but that resulted in lots of area being inside the light’s radius. That makes for a discontinuity in lighting that shows up as a circle on the ground at the edge of the light. Using the center distance makes the “radius” term somewhat inaccurate, but it still does what content creators expect in relative terms.

March 19, 2013 at 11:35 am

How do you define Li in dmax equation ? max(r,g,b) ? luminance = dot(rgb, vec3(0.2126, 0.7152, 0.0722)) ?

March 19, 2013 at 12:26 pm

I think I used max(r, g, b) at the time, but there was no justification for that choice.

Really you should just define it in whatever way works best for you and your artists.

March 19, 2013 at 5:08 pm

Let’s assume that r = 1, Li = 10 (HDR), cutoff = 0.01. Then dmax = ~30.6. Theoretically the attenuation for d = 20 should be greater than 0, but scaling and biasing attenuation give us 0. Maybe better solution is: attenuation = (attenuation – (cutoff / Li)) / (1 – (cutoff / Li)) ?

March 21, 2013 at 7:47 pm

First. I appreciate your feeling sorry for Feiner and Hughes, but I’m pretty sure Steve and I have never felt all that bad — working on the second edition was, for me, a great opportunity right near the start of my career in graphics.

Second, this is a nice bit of work, but it won’t appear in the new edition — not because I don’t like it, but because we’re now at the copyediting phase of production, and if I tell my editor I want to add a few pages, I’ll probably have to pay the hospital bills.🙂

As a small compensation, in the chapter about light and its measurement, we do an exact computation of the radiance leaving a spherical source, and I believe there’s an exercise in which the student gets to analytically integrate the cosine-weighted arriving radiance at a plane beneath that spherical source, so if you do this exercise, you sort of get a shot at learning the falloff rule (and being able to generalize, at least if you like integrating!). A key lesson is to realize that unless the ratio d/r is quite small (glowing bubbles above a tablecloth?) the attenuation term looks a LOT like 1/d^2. For the room I’m sitting in, d/r is approximately 20, for instance. Not small enough to make a significant difference.

Keep up the good work!

March 21, 2013 at 10:26 pm

Thanks for the comment John, just knowing that you’ve read my post is more than enough compensation.

I wasn’t aware that a third edition of Computer Graphics was on the way, I look forward to its publication. My second edition copy is a valued possession, and one of the few books I took with me when I moved across the Atlantic from England to the US a few years ago.

My maths skills have never been more than mediocre, so if the new book does have an exercise that leads the way towards a mathematical understanding of the falloff (more than my ‘hey, these graph plots look alike’), I will give it my best shot. I shall have to brush up on my surface integrals in preparation🙂

September 7, 2013 at 3:53 am

[…] Вот ещё неплохой пост на тему: https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/ […]

September 16, 2013 at 11:13 pm

[…] https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/ […]

August 15, 2014 at 1:19 pm

“Unfortunately this attenuation model is still problematic for real-time rendering, since a light’s influence is essentially unbounded.”

I’m wondering if you could expand on why this is an undesirable effect? Sure you’re cutting off below a threshold, but aren’t you still running the pixel shader for those pixels? Why not just let the little fraction of light shine through as it will hardly contribute to the result (and be more physically plausible)?

I could see if you optimized your light loop to not include lights over a cutoff distance however…

August 16, 2014 at 7:50 pm

This was a toy example so yes, I was running the fragment shader for all pixels.

Like you say, in more complex scenes you would want to avoid performing shading computation for as many pixels as possible.

July 30, 2016 at 3:40 am

Hi,

it took me a while to figure out a proper value for the cutoff. At first, my value was too high and as a result I could not see any light contribution (attenuation was close to zero).

I finally came up with the following formula, based on the light’s radius and maximum range:

float denom = range + radius;

float cutoff = ( radius * radius ) / ( denom * denom );

This is derived from your formula’s and therefor fully compatible.

-Paul