I have to render some objects over top over others, along with the ability to change this at runtime. I cannot do the following common tricks you can find online by googling:
The “disable depth test” with translucency trick. This cannot be changed at runtime.
For gameplay reasons, I also cannot scale the objects and put them close to the camera.
Many people seems to suggest Custom Depth for this, which is puzzling to me because Custom Depth simply is not able to be used for this.
Right now I am doing the third trick you can find online - some math in world position offset, so if a vertex of the material would be behind another object, it is moved to render in front. This technically gives me what I want, but it has some drawbacks as well:
It’s not exactly a simple calculation, it’s disabled in 99% of objects 99% of the time, and so I have concerns that this could cause significant overhead and waste if I put it in all the materials that need this functionality.
The object stops rendering when the bounds are fully occluded by something, causing it to simply pop out of existence. This could probably be alleviated by setting bounds scale, but sill leaves the material instructions overhead problem.
Any ideas? I’m not averse to doing some engine modding if it comes to that, but I would need to be pointed in the right direction.
I’ve seen similar question before, people say the Engine intend to render things physically. Which means there is no direct solution integrated, and zbuffer is well optimized for this so you can’t easily modify the pipeline. So one may only do some tricks at certain situation. As for me, I use a custom stencil at 1 on the actor should be revealed, and a simple lerp to scenecolor on those actors which might be covering it up. This method might be a good vesatile one, but will cause some translucent consume. If you manage it well, I believe things won’t get messy.
Unfortunately, this solution is not workable in my case for a couple different reasons: The objects that need to render over everything could clip through any given set of materials at any time - it is not feasible to make every object in the scene translucent. Additionally, it will cause visual artifacts if you make use of material-based post-processing such as outlines, since the SceneColor node shows only the color before any post process materials are applied.
I have found a solution. I want to take some time to do a more comprehensive write-up at some point, but here’s the broad strokes:
We are essentially going to use a render target to fake a render layer such as what you may find in another engine like Godot or Unity.
First, a couple caveats: this method will not work with any translucent materials on your meshes that you want to render over everything. Translucent materials will simply be invisible. This is unfortunate, but minor in my case. Also, using a render target of this size in this way may introduce significant overhead. In our case it was okay, but it’s up to you to profile and ensure that it will hit your project’s performance targets.
On begin play of your CameraManager, create a render target with a size equal to the platform’s native resolution. I had to write my own c++ function to get the native resolution, as the built-in GetDefaultResolution was returning 0, 0 for some reason. You can probably find a way around this in BP by simply keeping track of your last resolution setting. Default color settings are fine.
It is important you do this with blueprint nodes rather than creating a render target asset in the editor, because different people will have different native resolutions based on their monitor.
Add a SceneCapture component to your CameraManager. It is important you add the component in here rather than in your character, as the CameraManager’s transform matches with any camera animations such as screen shake.
Using blueprint nodes, set the render target you created as the target texture for the SceneCapture component. This is shown in the screenshot in Step 1.
Set the SceneCapture’s color mode to any of the FinalColor modes, and set the SceneCapture’s show mode to ShowOnlyActors. Then populate the array with the actors you wish to render over everything (for example, your player character).
To avoid objects being rendered to both the RenderTarget and your player’s normal camera, causing double rendering, make sure that all mesh (both skeletal and static) components you wish to render over everything have “Visible in Scene Capture Only” checked.
It’s not apparent yet, but we have a bit of an issue. If you were to attempt to draw your RenderTarget to the screen now, you will find that it has no Alpha Channel. This is because the FinalColor modes on the SceneCapture component do not include opacity. We will have to generate our own mask. We will use CustomDepth for this.
Create a new PostProcess material - RenderOverMask. This material will ensure that the SceneCapture component perfectly renders 0 in all RGB channels. Set it as the Post Process Material in your SceneCatpture component. We are doing this because depending on your post process settings, the background of your RenderTarget (the portion that does not render anything) may not be pure black (mine was a deep maroon). Thus, we use the CustomDepth stencil to ensure that everything not rendered is exactly 0 in all RGB channels.
Create a new PostProcess material. This will be the material that renders your RenderTarget to the player’s screen. Add a texture sample to it. You will be setting this equal to your created render target later. Below, I named mine “Objects”. The Alpha portion of the lerp is length of the Render Target’s channels, slightly blurred, multiplied by a large number, and clamped from 0-1. Why are we doing it this way? Why not just use custom depth? Well, this material is on the player’s camera, not your SceneCapture component. Thus, the CustomDepth objects will not appear here since we enabled “Visible in Scene Capture Only” in step 5. So unfortunately, we kind of have to BS our way into a mask. The SpiralBlur is to help fill in any holes from your RenderOver meshes hitting exactly 0 in their RGB channels. The 5000 multiplier is just to tighten the mask and ensure it is always 0 or 1. 0 * 5000 = 0. Thus, our material in step 8 ensures our mask stays intact.
Finally, put the PostProcess material on the player’s screen using your preferred method - I am using a CameraModifier I have named RenderOverModifier. Do not forget to set the RenderTarget as the texture parameter in your material using a Dynamic Material Instance:
There are some other details I have yet to work out, such as how it looks at lower resolutions, etc. But for now it seems to scale down fine so long as the player sticks to resolutions that match their monitor’s aspect ratio. It’s possible you could refresh the render target on resolution change to mitigate that. You may even be able to provide a separate resolution scale for this to help people with lower end hardware.
You can add/remove actors to this fake render layer at runtime like so: