One of the core gameplay hallmarks of modern stealth games is the mechanic whereby you can âtagâ enemies or other objects and then see them through walls - you could call it âx-ray visionâ or âstealth game visionâ. You could implement the effect in many ways, and in this tutorial, Iâll show you one approach that works in Universal Render Pipeline. Weâll be using Renderer Features to do this.
For this tutorial, we are using Unity 2021.3.0f1 (LTS) and URP 12.1.6.
Check out this tutorial over on YouTube too!
Renderer Features
With Renderer Features in URP, we can force objects in specific layers to render on top of everything else if they are positioned behind a wall. First, letâs set up a layer and add at least one object to it to test the effect. For this example, Iâll use a character mesh. Click on any object and go to Layer -> Add Layer at the top of the Inspector, then create a new layer named âRenderOnTopâ (or whatever you want) and assign all your tagged objects to this layer. In a real game, you would need to build a gameplay system that lets you add or remove tags from objects or enemies, but Iâll focus only on the shader part in this article.
Next, find your projectâs URP Renderer asset, which can be found in the Settings folder in a brand-new URP project. Before we can deal with X-ray vision, we must first disable the normal rendering of all objects in the RenderOnTop
layer, which we can do by deselecting that layer from the Opaque Layer Mask and Transparent Layer Mask dropdown settings. Once you do that, the objects in those layers should pop out of existence.
Now we will add back rendering the objects when they are behind walls. On your URP Renderer, at the bottom, go to Add Renderer Feature -> Render Objects, which is a special Renderer Feature provided by Unity that lets us override certain aspects of the core rendering loop. Name it âHighlight Opaqueâ and set the Event to AfterRenderingOpaques. In the Filters dropdown, the Queue should be Opaque and the Layer Mask should contain only the RenderOnTop
layer. Essentially, these settings mean that Unity will try to draw all the opaque geometry in the RenderOnTop
layer after all other opaque objects have been drawn - but thatâs just when it will draw them, not how it will draw them.
In the Overrides dropdown, weâll add some crucial behavior. First, drag a test material into the Material slot (Iâm using an unlit white material for now), then in Depth (which you should tick), weâll modify the depth test. We want to render only if the depth of a RenderOnTop
object is greater than the depth of any existing opaque object, so change the Depth Test to Greater and make sure that Write Depth is left unticked.
Once youâve done that, youâll be able to see your objects again, but only if they are behind a wall.
Now weâll add back normal rendering of the layer whenever objects are not behind a wall. Add a second Render Objects feature and name it âNormal Opaqueâ, then give it the same Event, Queue, and Layer Mask settings as the first feature. This time, in Overrides, leave the material as blank because we donât want to replace the objectsâ regular material, and set the Depth Test to Less Equal. We should also write the depth value so tick that too.
Now, we can view objects normally when they are not hidden behind a wall, but the moment parts of the object are obscured, weâll see the unlit white material.
If you plan to use transparent objects in the RenderOnTop
layer, then weâll need to add two more Render Objects features; duplicate the existing two but change the Event to AfterRenderingTransparents and swap the Queue to Transparent. You might also want to rename them âHighlight Transparentâ and âNormal Transparentâ respectively.
The basic effect is complete, but I also want to bundle in a couple of shaders to use for the X-rayed objects because unlit white is a bit uninspiring!
X-ray Material
This shader is a fairly static shader that helps to highlight the edges of the object. That way, itâs more stylistic than a basic color, but it still helps you to establish the silhouette of the object. Right-click the Project View and select Create -> Shader -> Universal Render Pipeline -> Lit Shader Graph, and name it âStealth Visionâ. Both the shaders we will create should be transparent, so go to the Graph Settings and change the Surface to Transparent.
This shader is straightforward - all I want to do is add an emissive Fresnel effect to the object. Start by adding a Base Color
property and make sure it is HDR-enabled, then add a Fresnel Power
Float
property too - this will control the thickness of the outlines. Drag the Fresnel Power
property onto the graph and use it as the input to a Fresnel Effect
node, then multiply it together with the Base Color
property. Output the result to the Emission node of the master stack, then use a Split
node to connect the alpha component to the Alpha output block. The shader is now complete!
We can see it in action by choosing a high-intensity red color on a material using this shader, then dragging it onto the material override on the Renderer Features in place of the unlit white material we were previously using. This material works best on organic objects with some curves, as Fresnel is only apparent on flat surfaces viewed at certain angles.
Lava Lamp Material
This second shader is a little more complicated and features some animation. Like the first shader, it is also transparent. It uses a similar HDR-enabled Base Color
property, as well as a Float
property called Speed
to control the animation - give it a default value of 0.25. Later, we will also incorporate two more Float
properties called Noise Scale
and Thickness
.
First, drag the Speed
property onto the graph and multiply it by a Time
node. If we Modulo
the result by 1, then it will count to 1 and then loop back to 0 before ticking up again continually. Next, Iâll be using a noise cloud to define the pattern this effect uses. Add a Simple Noise
node with the Noise Scale
property in its Scale pin, then output the result to the In pin of two Step
nodes. Here, we will provide two thresholds.
For the first Step
node, pass the modulo clock we just made into its Edge pin. Then, for the second Step
node, add the Thickness
property to the modulo clock and use that for the Edge pin. Now we have two thresholds, and one uses a higher value than the other. If we take the second Step
and use One Minus
on the output, then multiply it with the first Step
node, we have essentially found all values that fall within both thresholds. In our case, this is a thin slice of noise values. We can output this value to the Alpha output block, then multiply by the Base Color
property and output the result to the Base Color output block.
You can leave things there, or for a slightly more interesting pattern, go back to the start of the graph and use a Position
node in world space as the input to a Polar Coordinates
node, then use the result for the UV slot of the Simple Noise
node.
That should give you more curvy patterns.
Conclusion
The Render Objects feature makes it rather easy to override rendering settings. Conventionally, you would have to write different depth settings directly into your shaders and then attach two materials to your objects, but we managed to avoid that here with Render Objects. The biggest advantage here is that we can swap out the material of every object to an X-ray material, no matter what material those objects started with, although itâs a shame that Unity has a hard limit on the total number of layers, which might be a problem in some projects (especially as the layers are shared by the physics system, which is a bizarre design choice). I also used Render Objects for my Impossible Geometry tutorial if youâre in the mood for more!
Subscribe to my Patreon for perks including early access, your name in the credits of my videos, and bonus access to several premium shader packs!
Acknowledgements
Special thanks to my Patreon backers
for March - April 2023!
JP kai Jack Dixon Juan Huang Morrie Mr.FoxQC Josh Swanson Moishi Rand Alexis Lessard Jay Liu Mikel Bulnes Ming Lei Muhammad Azman Olly J Paul Froggatt Will Poillion Zachary Alstadt ćş ĺ