Make a Node2D dynamically draw on top of everything, no matter what

Godot Version

v4.4.1.stable.mono.official [49a5bc7b6]

Question

I’m sure the answer is quite simple and it’s staring me in the face, but I just can’t see it.
I made a system that will focus on an item with the camera, and I want to make it so the item it focuses on should also get full priority in terms of rendering. This means drawing on top of EVERYTHING else, no matter what.

I thought this would be easy, but using Top level isn’t an option, as it moves the node to the middle of the current scene, modifying it’s position, which is obviously not ideal, and besides, it’s still not drawing over the blur.

Changin it’s VisiblityLayer seems to do absolutely nothing.

Changing the Z Index to a massive amount isn’t working either, as it still ignores the shader I have, which blurs the screen, and it’s in a CanvasLayer, with a Layer of 95. (I set Z index to 1000 when trying to draw this node on top)

Is there a simple way I can do this, which is also easy to reset once the focus period expired?

Here’s the current “solution” I have, but it’s more of a hack, and I really don’t like it, as it involves duplicating the node and placing it on another CanvasLayer:

private void ExcludeItemFromBlurEffect(Node2D nodeToExclude)
{
    CanvasLayer canvasLayer = new();
    canvasLayer.Layer = 1000;
    
    Node2D duplicateNode = nodeToExclude.Duplicate() as Node2D;
    
    nodeToExclude.AddChild(canvasLayer);
    canvasLayer.AddChild(duplicateNode);

    _nodeWeAreExcluding = nodeToExclude;
    _duplicatedNode = duplicateNode;
    _keepDuplicateOnScreen = true;
}

private void ResetBlurExclusion(Node2D nodeToReset)
{
    Array<Node> children = nodeToReset.GetChildren();
    if (children.Count == 0)
    {
        return;
    }
    
    _keepDuplicateOnScreen = false;
    _nodeWeAreExcluding = null;
    _duplicatedNode = null;
    
    foreach (Node child in children)
    {
        if (child is CanvasLayer canvasLayer)
        {
            canvasLayer.QueueFree();
        }
    }
}

Then in process, I just call this function during the focus:

private void ScaleAndKeepItemFocusedWithBlur()
{
    _duplicatedNode.Position = NodeHelpers.WorldToViewport(_nodeWeAreExcluding);
    _duplicatedNode.Scale = Zoom;
}

Surely there’s a better way?

1 Like

If my understanding is correct, canvas layers are somewhat comparable to draw calls, in the sense that they’re a rendering “layer”, drawn sequentially. The z-index of a node is NOT a global draw index, but rather it’s draw index relative to it’s canvas layer (e.g., a node in layer 1 with z-index -10000 will still display on top of a node in layer 0).

When you use a “blur” canvas layer, all it’s doing is grabbing the screen texture for the layers which have already been drawn (less than 95). There is no z-index value which would allow a node to opt-out of this effect, since fundamentally the effect is simply operating on the screen texture, which has already been captured!

Normally the structure would look something like this:

  1. World
  2. Blur shader
  3. HUD

One solution would be to calculate the zone of the scene that should be opted out of the blur (calculate on CPU, and pass as shader param to the GPU).


Note: I think your ‘hack’ could probably be simplified by using reparent?

1 Like

Issue with Reparent is that certain objects are expected to constantly stay where they are, as they’re referenced by my script for certain events. Neither solution is perfect, as duplicating means that, for example, if my script was animating this Node, it won’t animate anymore, and if I reparent, the game could potentially crash in some cases because of a null reference exception.

There are other possibilities. You could for example attempt to “mark” the node in some way (e.g., give it a specific color in the red channel, which you don’t use anywhere else), and then in the blur shader, remove the red tint, and of course don’t apply the blur on nodes with this tint.

Or you could perhaps make some kind of screen capture/back buffer copy which only captures the node to be displayed, and then add that to a layer on TOP of the blur. This would be similar to duplicating, but instead of duplicating the node, you’re essentially just taken a texture of it, and re-displaying that higher in the stack.

I’m far from an expert on this topic though, so leave the discussion open and maybe somebody with more expertise will drop by.

1 Like