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.

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!