Steve McAuley

Computer graphics in the real world

Extension to Energy-Conserving Wrapped Diffuse

2 Comments »

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:

Valve's wrap shadingValve's wrap shading with energy conservation

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

Sloan's wrap shading with a = 0.5Sloan's wrap shading with a = 0.5 and energy conservation

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

Generic wrapped diffuse with w = 0.5 and n = 4.0Generic wrapped diffuse with w = 0.5, n = 4.0 and energy conservation

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