# Steve McAuley

## Extension to Energy-Conserving Wrapped Diffuse

A while ago, I wrote a blog post on energy-conserving wrapped diffuse lighting [1], noting that following standard formula ended up adding a lot of energy:

$\frac{\cos(\theta) + w}{1+w}$

Instead, we should normalise and use this instead, preventing a sudden surge in energy whenever we decide to wrap:

$\frac{\cos(\theta) + w}{(1+w)^2}$

However, Jorge Jimenez recently pointed out to me that it would be much better if we could generalise the energy-conserving wrapped diffuse model to encompass a slightly more complex model for diffuse lighting, one using a power:

$(\frac{\cos(\theta) + w}{1+w})^n$

In fact, raising your wrap shading to a power is a pretty common thing. It makes things look a little bit softer and more pleasing on the eye, as Lambertian diffuse tends to have quite a harsh fall-off that you’re trying to get rid of with wrap shading. Valve did it [2], and that was extended by Sloan et. al. [3] who used:

$(\frac{\cos(\theta) + w}{1+w})^{1 + w}$

whereas Valve’s original model fixed w as 1.0. We can do better than both of those by making our formula completely generic, allowing us to use any combination of wrap factor and power that we want.

Once Jorge had made the suggestion of extending energy-conservation to this formula, I quickly pulled out my pen and paper and scribbled down some integrals, and to my surprise it all fell out pretty nicely. Naturally, it’s very similar to what’s in my previous post, so forgive me if I skip a few steps…

First, remember that we need to integrate not just over the hemisphere, but further around the sphere until the formula hits zero (at which point in our shader we clamp). So rather than integrating between 0 and ½π, we go from 0 to α, where α is such that:

$\cos(\alpha) = -w$

Now we can begin, and we’ll again normalise by π now so we don’t have to worry about it at the end:

$\frac{1}{\pi} \int_{0}^{2\pi} \int_{0}^{\alpha} (\frac{\cos(\theta) + w}{1+w})^n \sin(\theta) d\theta d\phi$

After we’ve got rid of the outer integral, and pulled out what we can of the inner, we’re left with:

$\frac{2}{(1+w)^n} \int_{0}^{\alpha} (\cos(\theta) + w)^n \sin(\theta) d\theta$

We now pull our main trick: let’s substitute $x = \cos(\theta)$, which gives us $dx = -\sin(\theta) d\theta$ and therefore get the following equation:

$-\frac{2}{(1+w)^n} \int_{\theta=0}^{\theta=\alpha} (x + w)^n dx$

We only have to remember Integration 101 to work that one out, as long as we make a tiny assumption about our value of n (it can’t be -1):

$-\frac{2}{(1+w)^n} \left[ \frac{1}{n+1}(x + w)^{n+1} \right]_{\theta=0}^{\theta=\alpha}$

Now, if we put back in our value for x and evaluate using our identity for α, we get the beautifully simple:

$\frac{2(1 + w)}{n+1}$

Just to check if we’ve got this right, we’ll try n = 1, as in my original post. Using the above formula, we still get $1 + w$, the same as our original answer, so we’ve probably done something right!

Here’s some obligatory shader code for the new model:
 // w is between 0 and 1 // n is not -1 float3 wrappedDiffuse = LightColour * pow(saturate((dot(N, L) + w) / (1.0f + w)), n) * (n + 1) / (2 * (1 + w)); 

Finally, let’s compare the differences between the old models (on the left) and our new energy-conserving models (on the right).

Valve’s model:

Sloan et. al.’s model, with a = 0.5:

Generic model, with w = 0.5 and n = 4.0:

The differences are a lot more subtle this time (especially with the values we’re using) but they’re still there. It’s worth noting that although for the first two models, energy conservation is making the lighting darker, in the last model the lighting becomes brighter. So don’t make any presumptions!

If you want to have a play around yourself, then here’s a RenderMonkey workspace that allows you to tweak the power and wrap factor, and turn on and off energy conservation.

Now surely, there’s no excuse left to use a non-energy conserving wrapped diffuse model?

References

[1] http://blog.stevemcauley.com/2011/12/03/energy-conserv…rapped-diffuse/
[2] Jason Mitchell, Gary McTaggart and Chris Green, Shading in Valve’s Source Engine, Advances in Real-Time Rendering in Graphics and Games, SIGGRAPH 2006, http://www.valvesoftware.com/publications/2006/SIGGRAPH06_Course_ShadingInValvesSourceEngine.pdf
[3] Peter-Pike Sloan, Derek Nowrouzezahrai, Hong Yuan, Wrap Shading, Journal of Graphics Tools, Volume 15, Issue 4, 2011, http://www.iro.umontreal.ca/~derek/files/jgt_wrap.pdf

## Energy-Conserving Wrapped Diffuse

I love physically-based rendering, and one of the most important aspects is making sure all your lighting is energy-conserving. Otherwise, you’re prone to end up with things that look too bright or too dark, and struggle with consistency in the look of your game.

One topic I’ve never seen covered when talking about energy conservation in games is wrapped diffuse lighting. It’s admittedly a bit of a hack, but it’s incredibly useful for lighting things like particles where a simple Lambert term doesn’t come close to representing light scattering through smoke. Particles are also effects that are no strangers to inconsistency – often made by a separate team who have to juggle with special case materials and lighting models, what hope do they have?

Unsurprisingly then, this issue reared its ugly head when I was implementing wrapped diffuse lighting for particles. One of our effects artists pointed out to me that they were looking far too bright compared with the rest of the scene. For example, compare these two spheres:

On the left we have our standard trusty diffuse lighting, whereas on the right we have wrapped diffuse – the wrapping is pretty extreme, in fact, as extreme as you can get, but it illustrates the point nicely. With wrapped diffuse we’ve added so much extra light onto the sphere! Thankfully, I was already sceptical about the energy-conserving nature of the default wrapped diffuse model, so I knew exactly where to start looking at the problem and pulled out a trusty pen and paper to run the maths.

First, let’s remember what performing standard diffuse lighting looks like.

float3 diffuse = LightColour * saturate(dot(N, L)); 

However, this isn’t energy conserving by itself. For it to be so, we need the integral over the hemisphere to obey the following rule:

$\int_{\Omega} \cos(\theta) d\Omega \leq 1$

If we rewrite this as a double integral in polar coordinates, we find out that

$\int_{0}^{2\pi} \int_{0}^{\frac{\pi}{2}} \cos(\theta) \sin(\theta) d\theta d\phi = \pi$

(This is explained in more detail, including why we need that mysterious extra sine term in [1]).

So actually, standard diffuse lighting doesn’t conserve energy – we actually need to divide by π. However, this is just a constant factor so we usually just ignore it and assume that our lights are just π times too bright. Yeah, this is a little confusing, but it’s one of those little annoyances you just have to remember… especially when you do energy-conserving specular and you have to remove factors of π from those equations too.

Now we can turn our eyes to wrapped diffuse lighting. We adjust our cosine term (dot product between the normal and the light) so that the light wraps around the sphere by an adjustable amount. The code is as follows:

// w is between 0 and 1 float3 wrappedDiffuse = LightColour * saturate((dot(N, L) + w) / (1 + w)); 

For this equation, we can’t just integrate this over the hemisphere to calculate our energy conservation factor – now the lighting is wrapped so it extends further around the sphere. So instead of integrating between between 0 and ½π, we need to integrate between 0 and α, where α is such that:

$\cos(\alpha) = -w$

Or in other words, we integrate around the sphere until the lighting becomes zero, then we stop.

So let’s get started! First, we’ll normalise by π right at the very beginning so we don’t have to worry about it at the end:

$\frac{1}{\pi} \int_{0}^{2\pi} \int_{0}^{\alpha} \frac{\cos(\theta) + w}{1+w} \sin(\theta) d\theta d\phi$

We can immediately evaluate the outer integral and pull a constant term out of the integral completely:

$\frac{2}{1+w} \int_{0}^{\alpha} \cos(\theta) \sin(\theta) + w\sin(\theta) d\theta$

Using a useful trigonometric identity, this becomes:

$\frac{2}{1+w} \int_{0}^{\alpha} \frac{1}{2} \sin(2\theta) + w\sin(\theta) d\theta$

$\frac{2}{1+w} \left[ -\frac{1}{4} \cos(2\theta) - w\cos(\theta) \right]_{0}^{\alpha}$

Evaluating this leaves us with:

$\frac{2}{1+w} \left( -\frac{1}{4} \cos(2\alpha) - w\cos(\alpha) + \frac{1}{4} + w \right)$

Another trigonometric identity allows us to get rid of that pesky 2α:

$\frac{2}{1+w} \left( -\frac{1}{4} (2\cos^2(\alpha) - 1) - w\cos(\alpha) + \frac{1}{4} + w \right)$

Now we can use our identity for α to remove all the cosines completely:

$\frac{2}{1+w} \left( \frac{1}{2} w^2 + w + \frac{1}{2} \right)$

This collapses down to the amazingly simple:

$1+w$

That’s a really, really nice result, and means we simply have to divide by the above to achieve energy conservation. Thus when writing code for wrapped diffuse lighting, it should really be:

// w is between 0 and 1 float3 wrappedDiffuse = LightColour * saturate((dot(N, L) + w) / ((1 + w) * (1 + w)));

If we think about it for a bit, this actually makes sense on a purely intuitive basis. If we wrap our lighting all the way around the sphere, so w = 1, then we get a normalisation factor of

$\frac{1}{1+w} = \frac{1}{2}$.

So our lighting is covering twice the surface area (the full sphere instead of just the hemisphere), and thus it is half as bright.

Let’s look at some pictures to see what it looks like with a couple of different values for w. Standard wrapped diffuse lighting is on the left, whilst our new energy-conserving model is on the right:

For w = 1.0:

For w = 0.5:

Much better!

After all that, we can use wrapped diffuse on our particle effects, or anything else we choose, fully confident that they’ll blend in properly with the rest of the scene, with the added benefit that the cost of this extra energy conservation term is minimal (or free, if you don’t need w to be variable).

Hopefully now I’ll never see the old wrapped diffuse model used again!

References