outline_feature_4

in Tutorials, UE4

Outline effect (Part 4)

We have received several requests for the outline effect tutorials so we have decided to open a new tutorial talking about two of this requests: Opaque zones and distance effect.

Part 1: Outline effect
Part 2: Occlusion, colors and glow
Part 3: Opaque zones
Part 4: Depth limit

Depth limit

ViewFrustum


Our first task is to understand how to measure the depth. The value of the Custom depth is relative to the camera view position, to measure this we can draw (use your imagination) a frustum from the camera position. The height of the frustum is the maximum depth to witch the outline effect will be applied.

depth_frustum

One option to see the limit in realtime is to add a plane to the character camera with a translucent material. For our example the X-coord will determinate the effect frontier.

depth_limit_panel

Now in the scene we can see a red translucent panel to indicate the limit of the effect, if the character moves around the map this plane goes with it.

preview_plane_limit

The unreal material

We can start making a material to paint the area effect. With this simple material we can see easily the zone our effect will be applied, we have set a maximum depth of 500 for this example

material_depth_limit_alt

Using a more elegant way but with the same result

material_depth_limit

As we can see now the farthest chest has no visible outline effect

depth_limit_preview

To apply this limit to our oultine-with-occlusion material we need to replace the SceneTexture:CustomDepth of the above material with a new HLSL node, this is necessary because we don’t have any depth information in the line of the outline effect, it has been added in realtime to the object and does not belong to it, does not have Custom Stencil or Depth value. So we need a variation of the Neighboring pixel calculation node, we must to use the custom depth values of the object surface to calculate the “depth” for each pixel of the outline.

hlsl_depth_detail

So instead of use only the Custom Stencil value…

float TL = GetScreenSpaceData(ScreenPosition + float2(-offset_h, -offset_v), false).GBuffer.CustomStencil.r;

… we must add and inline check for the Custom Depth value

float TL = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, -offset_v)), false).GBuffer.CustomStencil.r > 0 ? GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, -offset_v)), false).GBuffer.CustomDepth.r : 0;

Finally the HLSL code of the Neighboring-depth-pixel-calculation:

#if SCENE_TEXTURES_DISABLED
    return 0;
#endif
float offset_h = SceneTexelSize.r * Thickness;
float offset_v = SceneTexelSize.g * Thickness;

float TL = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, -offset_v)), false).GBuffer.CustomStencil.r > 0 ? GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, -offset_v)), false).GBuffer.CustomDepth.r : 0;
float TM = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(0, -offset_v)), false).GBuffer.CustomStencil.r > 0 ?GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(0, -offset_v)), false).GBuffer.CustomDepth.r: 0;
float TR = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(offset_h, -offset_v)), false).GBuffer.CustomStencil.r > 0 ? GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(offset_h, -offset_v)), false).GBuffer.CustomDepth.r: 0;

float ML = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, 0)), false).GBuffer.CustomStencil.r > 0 ? GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, 0)), false).GBuffer.CustomDepth.r: 0;
float MR = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(offset_h, 0)), false).GBuffer.CustomStencil.r > 0 ? GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(offset_h, 0)), false).GBuffer.CustomDepth.r: 0;

float BL = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, offset_v)), false).GBuffer.CustomStencil.r > 0 ? GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, offset_v)), false).GBuffer.CustomDepth.r: 0;
float BM = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(0, offset_v)), false).GBuffer.CustomStencil.r > 0 ? GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(0, offset_v)), false).GBuffer.CustomDepth.r: 0;
float BR = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(offset_h, offset_v)), false).GBuffer.CustomStencil.r > 0 ? GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(offset_h, offset_v)), false).GBuffer.CustomDepth.r: 0;

return max(TL, max(TM, max(TR, max(ML, max(MR, max(BL, max(BM, BR ) ) ) ) ) ) );
material_depth_limit_detail

Bringing it all together

outline_occlusion_limit_material_

This modification can be used to simulate an “opaque” bubble around the character to limit the visible outline effect. It sets a limitation to the super-vision of the player to not reveal all the map secrets šŸ˜›

depth_limit_youtube_img

Tutorial files

Support this blog!

For the past year I've been dedicating more of my time to the creation of tutorials, mainly about game development. If you think these posts have either helped or inspired you, please consider supporting this blog. Thank you so much for your contribution!

Write a Comment

Comment

  1. Hello,

    On Unreal 5.3.2, I noticed when there’s no custom rendered depth objects, a square is present at the top left corner of the game.

    Am I the only one getting this issue?
    Thanks