godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
89.66k stars 20.78k forks source link

When using a SubViewport to show 2D scenes the normal map information is lost. #96976

Open tipspit opened 2 weeks ago

tipspit commented 2 weeks ago

Tested versions

4.3.stable

System information

Windows 11 - 4.3.stable.official Vulkan 1.3.280 - Forward+ - Using Device #0: NVIDIA - NVIDIA GeForce RTX 3060 Laptop GPU

Issue description

When using a SubViewport to show 2D scenes the normal map information is lost. SubViewport.get_texture() returns a ViewportTexture that does not contain the information regarding normal maps. For example, if I wanted to project a scene containing some Sprite2D and related textures (CanvasTexture containing diffuse_texture and normal_texture) onto a PlaneMesh using a SubViewport, currently there is no way to make it interact properly with the light coming on the mesh in the 3D environment, because the data about the normal maps is lost.

Here I am projecting a 2D scene on a SubViewport rendered on a mesh, there is no way to retrieve the normal map informations to make it react to the light in the 3D environment (the relative Sprite2D in the 2D scene has a normal map): image

On the contrary using a 3D scene it is possible to see its normal information and use it throught Rendering > Debug Draw > Normal Buffer: image

I have thought of a few solutions:

Steps to reproduce

Minimal reproduction project (MRP)

Here there is a little project with a SubViewport where both 3D and 2D scenes projection can be experimented. I animated the lights so that the results can be seen easily. bug_sub_viewport_normals.zip

tetrapod00 commented 2 weeks ago

I'm going to go with not a bug, but a missing or maybe underdocumented feature, actually.

The normals buffer is not even fully supported for 3D. If I try your MRP with a 3D subviewport on Compatibility, the normal buffer debug view doesn't work, because there is no normal buffer on Compatibility. The debug views mostly just work with what is easily accessible, they don't go out of their way to add new buffers (because of code complexity and performance overhead). Notably there is a "lighting info" debug view for 3D, but it's not implemented by accessing a lighting buffer, because godot uses a forward renderer and doesn't have that buffer. Compatibility: Godot_v4 3-stable_win64_3S8RRXqr8H Forward+: Godot_v4 3-stable_win64_EZQsRFPLi5

For Compatibility 3D, the engine just does not store the normal information after using it to calculate lighting. It should be the same for a 2D viewport on any renderer. In 3D, I also believe you can only get one texture worth of information out of a subviewport, so you can't make use of both the rendered color and the debug normals. So all three of your proposed solutions have some barrier to implementation, I'm not sure which would be the easiest to implement technically.

I believe all three of your solutions require the 2D pipeline to create a normals buffer in addition to whatever buffers 2D currently creates, which technically might not be simple at all. Your proposed solutions only differ in how that information would be accessed, they don't change the fact that it's not currently there to use.

Maybe someone more familiar with the internals of the renderer can confirm?

tipspit commented 2 weeks ago

That was my fear, I'm not an expert but it seems to me too that for the solutions I proposed the only way is to have a 2D normal buffer to draw from, if it doesn't exist it's a problem.

I don't know if someone who knows the engine better can propose a more viable solution?

All the other ideas I can think of are workarounds, I don't even know how feasible, and that seems to me to be not the best from a performance point of view:

tetrapod00 commented 2 weeks ago

Make a duplicate of the 2D scene in the SubViewport that contains only the normals data, and generate the texture via another SubViewport.

This sound like the right workaround. FYI the problems you list with this are real and also exist for 3D. In Forward+, for example, if you want to render another "buffer" you have to duplicate nodes or use a shader hack to check culling layers. This should be made easier by https://github.com/godotengine/godot-proposals/issues/10546 if implemented, but that would only solve the 3D version of the problem. I think the shader culling layers trick doesn't even work in 2D at the moment?