Making Of / 30 October 2020

The Last of Us inspired real-time flashlight bounce

Backstory: Going back all the way to 2014 when The Last of Us remastered released on the PS4, something particular caught my attention. The light from the flashlight the player carried had real-time GI bounce - basically shining the light into a dark room bounced around (or at least  mimicked that) and lit up the room! Here's an example from a screenshot in-game:


It's subtle (as it should be) but it's there


So I wrote my version of this in Unreal Engine 4 with some C++ and custom HLSL node. I did this a few years ago and looking back at it I'm sure there's a lot of room for improvement! 


My result:




Breakdown:

  •  Spawned a Scene capture 2D at the player's position and attached to camera
  • In C++, I did a ray cast every other frame to a certain distance in front of the camera
  • I used the Scene capture 2D to sample only the base color (with a defined view frustum and sample resolution) at the ray cast hit point
  • ran the sampled base color and stored it to a Render Texture
  • Ran the render texture through a shader and calculated the average color from the texture stored in render texture (every frame)
  • separated out the output of the above step to R,G,B buckets and created a Light function material from it
  • I spawned 3 movable non shadow casting, diffuse only point lights each assigned with the light function material from the corresponding R,G,B buckets 
  • Make sure the spawned point lights' positions are updated every other frame a few defined units in front of the ray hit location  
  • Well there you go! That's it


Some small parameterization that I did

  • Strength of all the point lights are scaled up and down based on the flashlight strength
  • currently the Render texture is sampled at 16 pixels (4x4) but this could be changed for more accuracy
  • Multiple bounce maybe?


The Light function:


Simple HLSL code to average colors from a texture: 

float redBucket = 0.0f;
float greenBucket = 0.0f;
float blueBucket=0.0f;
float i=0.0f;
float j =0.0f;for(i = -1.0f;i<=1;i+=1/Res)
{
for(j = -1.0f;j<=1;j+=1/Res)
 {
 float4 sample = Texture2DSample(Tex,TexSampler,float2(i,j));
 redBucket+=sample.x;
 greenBucket+=sample.y;
 blueBucket+=sample.z;
 }
}

return float3(redBucket/Res,greenBucket/Res,blueBucket/Res);

Notes:

Obviously this method isn't perfect and has a lot of room for improvement:

  • I ended up turning up specular contribution from the point lights to save on performance. So basically I don't get any indirect specular from the flashlight
  • The tool still takes to >2ms to render which can use some optimization
  • Would like to imitate specular bounce when flashlight is pointed at something like a mirror

Well! I had a lot a fun working on this! Thanks for reading! :D