UV Light Shader - Illuminating Hidden Details


In this project, I created a shader that reveals hidden messages and symbols, taking inspiration from games like Phasmaphobia and Ocarina of Time.

I got idea from one of my level designers Ediz, where he had a basic blacklight mechanic in one of his Resident Evil inspired projects called

"The Mansion". I gave him an early prototype version of the shader that we implemented into his flashlight blueprint. For this project I gave myself 3 weeks.

Intended Use


The setting I decided to go for was a supernatural detective game, where you would wander through a graveyard and find clues. The meshes, assets and level were from the 'Necropolis - Graveyard Kit' by Cryptic Realms on the FAB marketplace. I chose this because I found that it fit the theme I wanted and it shows how my shader can be implemented in finished scene. 

Custom textures for decals made in Substance Designer.

Cone Attenuation Mask 


This video shows the outer and inner cone angles for the blacklight mask. Inner cone angle (red) is the area of the mask where the decal is fully revealed, and the outer cone angle (green) is the cutoff point for the light. I chose this approach because it mimics how a UV flashlight would work in real life, where the light fades out further away from the center.

Debug view of cone mask area of effect.

Cone Mask material function.

Mask Workflow


In the player blueprint, the UV flashlights world position and direction, as well as cone angles and distance fade parameters, are sent to a Material Parameter Collection (MPC). The blacklight mask function then reads those values compares the directions of the flashlight and the direction of the pixels from the light (dot product). Then the shader makes the cone shaped fade by remapping the gradient from outer cone to the inner. After that, a distance fade is applied to the cone so that the glow gets weaker the further away it is. Finally the mask is sent to the output of the function and can be used in any material.

Material Function


To streamline the workflow of implementing the shader, I turned it into a material function that lets artists add it to a new/existing material making integration of UV light responsivness faster and user friendly. Additionally, the material function has centrilized updates, meaning if any changes are made to the cone mask's functionality, anything using the material function would automatically update instead of having to go into each material and make the change one by one, making it scalable.

Footprint mask for decal by Mikael Brunberg

Example of a decal material I made

Video of toggling UV responsive opacity.

Material Parameters


I added exposed parameters to the material function to allow the user to control the visibility of decals/materials. In this video I show the opacity override that toggles the visibility of the decals that use the material function. There are additional parameters that let the user change the cone/distance gradient falloff, as well as the min/max of the distance mask.

Dissolve VFX


I experimented with fake objects that dissolve when the flashlight is shining on them. For this I created a shader that gradually dissolves the mesh if it is within a distance threshold. I also added particles that sample the dissolving edge mask of the mesh to make it look like it is burning away. The dissolve effect is easily adjusted through a blueprint to an MPC.

Niagara Particle Emitters for Dissolve effect

Dissolving meshes with UV light.

Technical Challenges

  • Developing a user friendly material function for easy implementation on both new and existing materials.
  • Creating a cone mask function to mimic the behaviour of a light.
  • Managing the transfer of information from the CPU side (blueprints) to the GPU side (shader) efficiently.
  • Syncing parameters between the shader and particle system.

Takeaways

One of the most challenging parts of this project was figuring out how the reveal mask would work. One technique I looked into was hijacking a specific value in one of the render passes and saving that as a virtual texture that could be used as a mask in a post effect. I did not like this solution because it relied on the idea that everything that should react to the UV light needs to have that specific value, and everything else that was not supposed to react to the UV light could not have that value at all. The upside of having the cone mask function was that it was more scalable and did not require any strange values set in any of the render passes. 

Limitations and Future Plans


I would like to give the shader more functionality where it takes shadows into account. The effect works now because of the first person camera, but the effect breaks as soon as you look at the same spot in the world from an angle different to the direction the UV light is shining. If I were to implement shadow functionality, I would capture the scene from the UV flashlight's perspectives and save it to a render target. Then I would sample the texture and use it as a mask to only reveal pixels that are visible to the flashlight.