Last of Us style indirect shadows

As an experiment I tried to recreate the awesome indirect shadow system of the Last of Us:

This is based on this talk.

Still far from a complete implementation, but the basics are there.

how diff is this compared to the Ray Traced Distance Field shadows ue4 uses?
http://www.youtube.com/watch?v=-xai0iBffUc

Quite different, both technically and in intended use case. Those are great for hard to slightly soft direct shadows, while this technique is best suited for very soft shadows of indirect light. You could even use both of them at the same time, although the great thing about this technique is the way it works with baked lighting. Here’s an example of how it looks in the game that might explain it a little better then my simple test scene:

This effect is much more comparable to distance field AO. I also believe distance field AO doesn’t work in certain situations with skeletal meshes, you could use this technique to fill those gaps.

Wow this is awesome! Keeping an eye on this one. Thank you Arnage!!

If you can get it working with animated meshes + good performance I would gladly pay for your solution.

i want to see more of this in action :slight_smile:

This is a side project that I can only work on during my (sadly limited) free time, so don’t expect progress to be fast, but your feedback keeps me motivated to continue, so thanks :slight_smile:

The next step is to generalize the system to work with ellipsoids instead of just spheres, which is an important step towards getting it to work with characters.

This looks fantastic! Looking forward to seeing more.

Cool!!, would you have a side by side comparison of the original + your approach?

Sure, as the spheres are lit indirectly they basically just float without it:

Without.jpg 1b24b2983b508a47a8a2be9fbf4379a726628d87.jpeg

I did hit quite a roadblock though. I build the initial prototype as a material function that is added to the ambient occlusion input of a material. This works great for a quick test scene with few objects and materials, but it does not scale well at all. Instead I wanted to implement the shadows as deferred decals, but it seems like those not only can’t write to the AO buffer but the decal material also can’t access all the surface properties I need to calculate the shadows.

Another approach that would probably work is to implement the effect in a separate postprocess pass. This also has the performance benefit of being able to render the pass at a lower resolution with upscaling to provide a nice performance/quality trade-off. (Its strength is in the blurry effect anyway, so a significant lower res would probably not be that noticeable) There is just one slight issue with this approach: I haven’t really touched the engine’s source at all, so it’ll probably take quite a while before I understand the rendering pipeline well enough to actually do that…

It is looking good. Any progress?

Wow this is incredible! Are you planning on releasing it commercially or for free? Either way I would totally dig this :smiley:

Great work!!
Looks quite nice.

Skimmed the paper you posted an I am curious.

So the last of us setup said they mapped a bunch of spheres onto the character and used that. I guess that means each screen pixel needs to trace against every sphere for an intersection?

edit ah it doesnt actually raytrace to hit the spheres but rather knows analytically how much they occlude based on size/distance. that makes sense. very neat trick. And then they eventually skip the analytical step by baking the falloff to a texture. Did you get to the texture baking step yet?

I believe The Order used a very similar tech for occlusion and dynamic character reflections for the main character.

Yeah, good work!
Uncharted 4 is using the indirect shadow system from TLOU and I guess also the next AAA games will use it.

Properly implementing this got a bit bigger then I initially thought so to be honest I haven’t really been able to work on this much further. Additionally I noticed some work on surfel-based shadowing in the main source github, which might solve a similar issue, so I kind of focused on other things.

I’ll see if I can clean up the prototype and post the source here if someone else would like to attempt to create something more useful with it.

@RyanB: The images posted here used the lookup texture from the Naughty Dog presentation as I wanted to get the basics working first. My plan was to make a simple maxscript to automate the baking. Shouldn’t be to hard as you can basically bake a small coverage texture per pixel and store the average.

Hey guys, if anybody is curious to mess with this I made a material that can be used to render out the soft shadow lookup textures that are similar to the last of us paper (although not exactly).

The core of the math is in this custom node with inputs d, r0, r1:


float div1=cos(d)-(cos(r0)*cos(r1));
div1/=sin(r0)*sin(r1);

float div2=((-1)*cos(r1))+(cos(d)*cos(r0));
div2/=sin(d)*sin(r0);

float div3=((-1)*cos(r0))+(cos(d)*cos(r1));
div3/=sin(d)*sin(r1);

float term=(2*PI)-(2*PI*cos(r0));
term-=2*PI*cos(r1);
term-=2*acos(div1);
term+=2*cos(r0)*acos(div2);
term+=2*cos(r1)*acos(div3);



return term;

The material graph:

182cc4315e57b393ea58a3704bdb09226a3ce715.jpeg

a01216ddff2bf169d48911e5030674ba6c7ff99a.jpeg

Notice that the only modifiable variable still hooked up is the light cone angle. So you render multiple textures with various light cone angles and channel pack them and lerp between them to adjust the penumbra in realtime.

All cosine nodes have a period of 2pi so that they work the same as the custom one ones. Also notice all the starting degrees get converted to radians which makes it easier. And the UVs need to be given sphere like curvature which is why there is cosine on the UVs.

https://www.cse.ust.hk/~psander/docs/aperture.pdf

Here is a texture to try that has channel packed light source angle.

R=5degrees
G=15degrees
B=30degrees

A=60degrees

68c3a94926b4eff4f86f635e41e3be6bae87190c.jpeg

63019e1572cc5cee7db8c064f0c3c8c8d777ee86.jpeg

edit fixed curve mapping, it was slightly wrong before.