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

Calibrating Lighting and Materials in Far Cry 3

4 Comments »

This year at SIGGRAPH, Stephen Hill and I organised the Practical Physically Based Shading in Film and Game Production course. We’re really indebted to Naty Hoffman for putting us in touch with the speakers, and to the speakers themselves for making everything very easy for us, as well as producing such fantastic content. Most of all though, thanks to all of you for coming! I really couldn’t believe how full the room was, and I hope you learned a lot. If you haven’t found your way there by now, check out all the materials on the course web page.

I got to fulfil my personal ambition of speaking at SIGGRAPH by speaking on the work I’ve been doing on Far Cry 3:

Calibrating Lighting and Materials in Far Cry 3 [slides, (ppt, pdf)] [video] [course notes]

I want to give thanks again to Naty Hoffman for introducing me to the idea and the need of colour correction, but especially to Paul Malin at Activision Central Tech who developed the colour correction algorithm I used, and was kind enough to first share it with me so I could use it on Far Cry 3, and second to let me share it with all of you. Colour correction is something I think is incredibly important, and I’d love to hear what other studios are doing now and in the future.

Energy-Conserving Wrapped Diffuse

6 Comments »

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:

Diffuse lightingWrapped diffuse lighting

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

Now we’re ready to integrate!

\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:

Wrapped diffuse, unnormalised (w = 1.0)Wrapped diffuse, normalised (w = 1.0)

For w = 0.5:

Wrapped diffuse, unnormalised (w = 0.5)Wrapped diffuse, normalised (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

[1] http://www.rorydriscoll.com/2009/01/25/energy-conservation-in-games/

Around the World in 80 Shaders

No Comments »

There’s no doubt about it, writing shaders is fun. So why not go on a whirlwind tour around the world, looking at all the fantastic shaders we can write along the way? Hence Around the World in 80 Shaders, presented at Develop in Liverpool 2010.

I did have a confession to make at the start of the talk… I was actually only going to talk about six shaders, but hey, it was a good title! I also briefly discussed our awesome shader system that was a massive help to me and others at Bizarre who contributed to our shaders.

As such, the talk included shaders written and contributed to by a variety of people (including the shader system itself):

SPU Assisted Rendering

1 Comment »

Ste Tovey and I gave a talk at Develop in Brighton in 2010 on SPU Assisted Rendering. I mostly rehashed old ground, covering our SPU lighting implementation on Blur that was presented at Develop in Liverpool the previous year, while Ste talked about using the SPUs to help with vehicle damage.

A Bizarre Way to do Real-Time Lighting

No Comments »

At the inaugural Develop in Liverpool back in 2009, Ste Tovey and I presented our SPU lighting technique. Our initial implementation was of course on GPU, but inspired by Matt Swoboda’s work at SCEE R&D, presented at GDC earlier in the year, we realised that we could save a large amount of PS3 GPU time by moving the lighting to the SPUs. Blur was the perfect showcase and the end results speak for themselves.