godot-proposals icon indicating copy to clipboard operation
godot-proposals copied to clipboard

Add per-viewport `pre_draw` and `post_draw` signals

Open Astrono2 opened this issue 5 years ago • 2 comments

Describe the project you are working on: I'm working on a portal add-on.

Describe the problem or limitation you are having in your project: I'm rendering the portals with viewports and extra cameras. My current problem is that each camera's view through a portal is obstructed by the portal itself (I've disabled back face culling for two-sided portals). It would be easy to solve this problem using layers and cull masks, but that would require anyone using my add-on to have a free layer for each portal, which scales poorly when adding more portals, and it makes the add-on clunkier to use. The implementation I (as many others) am basing mine on (Sebastian Lague's) hides the portal plane before rendering the view form the camera and then shows it again, which he can do because he's rendering manually, which currently isn't possible in Godot.

Describe the feature / enhancement and how it helps to overcome the problem or limitation: Adding per-viewport pre_draw and post_draw signals wouldn't be as intrusive or big of a change as adding manual rendering, but it would still allow developers to hide and show objects before and after rendering a certain viewport.

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams: The already existing frame_pre_draw and frame_post_draw signals, get emitted at the beggining and end of the VisualServerRaster::draw and RenderingServerRaster::draw methods in 3.2 and master respectively. My proposal is to add two signals (pre_draw and post_draw) to the Viewport class and emit them at the beginning and end of VisualServerViewport::_draw_viewport and/or RenderingServerViewport::_draw_viewport methods (again, 3.2 and master respectively). I haven't checked any signal related code in the source, and was worried that emitting two different signals at the beginning and end of a method wouldn't work. But since that's how frame_pre_draw and frame_post_draw work, it seems like it's that easy, if people are interested I can make the pull request.

If this enhancement will not be used often, can it be worked around with a few lines of script?: As far as I know, no. For my project I'm gonna try to make a hacky workaround, since I want it to work without using a custom build. But I'm still thinking about how to do it, I'm open to suggestions. Maybe I'll just try to achieve the same result with a different approach, but I figure these two signals would be pretty useful for other projects anyway.

Is there a reason why this should be core and not an add-on in the asset library?: I don't know if it can be done with an add-on at all. I'm going to try, but even if it works, it's not going to be pretty. Implementing this in core is the easier alternative.

Astrono2 avatar Nov 11 '20 22:11 Astrono2

I'd love to have this!

I'm worried that there might be threading issues making this trickier than expected to implement. The VisualServer has frame_post_draw and frame_pre_draw signals, so it seems a bit of an oversight to not have something similar for Viewport already unless there's some problems adding them. That's pure speculation though :D

Zoomulator avatar Jun 25 '21 13:06 Zoomulator

Found this issue after trying to implement my own portal system. I would love to have this! When an object goes through a portal, I'd like to be able to move that object's global transform to be relative to the drawn portal inside of the frame_pre_draw function, and move it back inside of the frame_post_draw function.

Ribiveer avatar Jul 22 '24 13:07 Ribiveer

I've been attempting to implement this. and I think that the most clean and unobtrusive way to implement this is to emit a pre_draw signal here and a post_draw signal here. The Godot source code is still quite unknown territory to me, but I do understand the difficulty in implementing this.

There is the Viewport class, which inherits from Node. This is where we want to submit the signal from. This Viewport class contains a reference to a resource, a RendererViewport::Viewport struct. Those are iterated from the RendererViewport. Ideally, we'd be able to find the corresponding Viewport classes while iterating over them, but I haven't yet found a way to do this. I'll keep looking to find a clean solution to this!


I just realised this makes sense: one of the viewports we're talking about here is the main viewport. Of course it does not have a corresponding Viewport node!

I do have good news about the threading issues! The viewports are iterated using a simple for loop, and I'm not seeing any thread spawning inside of that loop. Thus, we can most probably safely emit a signal from there.


I think I just had an idea. Per-viewport signals aren't possible, since not every viewport is a node inside the scene. However, to my knowledge every camera is. Doing this signal per-camera satisfies the same requirements, but is likely much more manageable!


After some investigation, the cameras seem to work much the same way. Even worse, you'd expect Camera2D and Camera3D to share a common base class called Camera, but their most common base class is Node. There might be a hacky way to get their node by querying the scene or something silly like that, but this tells me that this is not the way to go.

I'm afraid this is where I stop playing around with this idea. I might revisit it at some point, but at the moment I'm lacking both the patience and familiarity with the source code to see this surprisingly tricky addition to completion.

Ribiveer avatar Jul 30 '24 16:07 Ribiveer

However, to my knowledge every camera is

It is possible to create cameras in RenderingServer without needing nodes.

Calinou avatar Jul 31 '24 15:07 Calinou

Well, then I suppose the most viable alternative is pre_viewport_draw and post_viewport_draw signals that have the RID of the viewport as parameter. Either that, or pre_camera_draw and post_camera_draw with the RID of the camera. I'm not sure which one covers more use-cases, or if there really is a notable difference in usability between the two.

Ribiveer avatar Aug 02 '24 15:08 Ribiveer

I'd imagine this being very useful for split-screen games which use sprites, eg. a multiplayer FPS similar to DOOM, where each actor's sprite should face the camera, but the animation displayed depends on its angle to the camera. As it stands, there's no simple way to accomplish this; a custom node that instantiates a sprite for each camera with a dedicated render layer for each would cover the vast majority of use cases, but the scene designer expects to be able to simply add an AnimatedSprite3D and tweak it directly; furthermore, the SpriteFrames editor is optimized for the animated sprite nodes, so upsetting that workflow isn't ideal.

caimantilla avatar Nov 24 '24 09:11 caimantilla

I'd imagine this being very useful for split-screen games which use sprites, eg. a multiplayer FPS similar to DOOM, where each actor's sprite should face the camera, but the animation displayed depends on its angle to the camera

This should be achieveable with a custom shader already, where the sprite decision happens on the GPU. I use this approach in https://github.com/godotengine/godot-demo-projects/pull/849.

Calinou avatar Nov 25 '24 18:11 Calinou

I was about to create a new issue, but found this one, so I'll comment here.

I tried experimenting with a second viewport through a camera interactable in my VR project, and I ran into some issues that could be solved if there were per viewport signals.

The player's head is set to cast shadows only, so that it doesn't get in the way of the VR camera, but this means that the streaming camera and other players won't be able to see other's heads at all, only the shadows.

https://github.com/user-attachments/assets/263fdcf3-4b9d-4eee-9c8e-a4f8b6451aaa

I also have some things that billboard towards the camera by setting their rotation to point towards the viewport's camera.

My idea was that there would be signals added to RenderingServer that also pass which viewport is being rendered.

If these signals were to be added, it would be possible to do billboarding for each viewport, and set the shadow casting mode for the head meshes depending on if the viewport being rendered has use_xr set to true, and if the player is local.

funkysandwich avatar Jul 19 '25 00:07 funkysandwich

I tried implementing the signals on a branch and I got them working.

However, due to how the RenderingServer queues certain types of instance updates, this prevents the signals from being as useful as they could be.

Certain things are only updated once per frame like mesh transforms and visibility. This means if you want to show or hide objects, or do billboarding on CPU like in my case, it will only work for the first viewport that gets rendered. Any further updates will get queued for next frame.

https://github.com/user-attachments/assets/34aad6db-1490-405b-80ee-9d8ae40d8992

Notice that the player name only faces towards the main viewport. I want to be do this on CPU rather than shader, because I'm building UI out of 3D nodes which will be made up of multiple meshes, and I want to rotate the root of the UI panel.

Other things like changing the shadow casting mode between viewports does seem to update immediately. Image I'm using this to hide the player's head mesh in first person/vr. I don't think there's any way to make a mesh cast shadows only for a certain viewport already.

I'm not sure if I should try making a PR for this given that updating certain things between viewports doesn't really work currently?

funkysandwich avatar Jul 26 '25 01:07 funkysandwich