godotengine / godot

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

Using Texture2D-less (e.g. Texture2DArray-based) ShaderMaterial for CanvasItem subclasses problematic #87792

Open FyiurAmron opened 9 months ago

FyiurAmron commented 9 months ago

Tested versions

System information

Godot v4.2.1.stable.mono - Windows 6.1.7601 - Vulkan (Forward+) - dedicated Radeon RX 580 Series (; 27.20.20912.1002) - Intel(R) Core(TM) i3-10100F CPU @ 3.60GHz (8 Threads)

Issue description

AFAIK, as it currently stands, it's highly problematic to have "just" a 2D shader without a Texture2D applied directly. For the sake of reference, let's assume I'll be using a shader sampling from Texture2DArray - which is not supported out-of-the-box to be used as texture, but obviously works when used in shaders by themselves. In that case, I need a 2D geometry to display the shader on, but I don't need to (or should) use a Texture2D by itself, since the shader samples from Texture2DArray and shouldn't and won't care about Texture2D set on the object anyway.

Note: In 3D, you need a reasonable geometry anyway 99.99% of the time, so using either one of the CSG or a mesh does the trick, since you can just put the shader in the material override and you're OK. In 2D however, most of the time (YMMV, but that's my experience) you're just displaying the textures etc. on rectangles, and only relatively rarely on some arbitrary polygons. Let's ignore the 2nd case for the time being, because for Node2D we have MeshInstance2D, which will work reasonably as Node2D subclass in this case, and having arbitrary n-gon laid out in 2D Control system doesn't really make much sense by itself; I think if you need that, having a rectangular bounding-box-like Control parent that would have the MeshInstance2D as a child is probably good enough, for such an edge case.

The main use case is then "I want to just display the shader on an arbitrary rectangle, either using the layout system (i.e. Control or its subclass) or not (i.e. Node2D or its subclass).". This would be the common case for Texture2DArray shaders, for shaders based on time, noise, gradients, generating various effects etc.

The tutorial docs say this:

To begin, create a Sprite2D node. You can use any CanvasItem, so long as it is drawing to the canvas

However, putting aside sometimes it's completely not obvious whether a CanvasItem Is or is not drawing to the canvas at all, this is not really applicable when dealing with Texture2D-less shaders.

The expected (and documented) way to handle this case would be:

The problem is, it just doesn't work ATM, at least not in a simple out-of-the-box way. I wasn't able to find any "dedicated" class that would handle this (like Shader2D, ShaderRect, whatever). Nothing gets displayed if I just use the root nodes (Node2D, Control) themselves. In case of Node2D this is I guess because there is no actual mesh with no size to apply the shader to; that's kinda expected. In case of Control, I'd say it's probably a bug or omission of some kind, prehaps intentional - the rect itself is there, but the shader is not applied to it at all.

Texture-based nodes (Sprite2D, TextureRect), as suggested by tutorial, can be used as a workaround, but they basically make no sense here; both display nothing if the Texture2D is not set, and there is no Texture2D here in this case... so you need to provide a PlaceholderTexture2D (with the needed size in case of Sprite2D, because the final size will be based on it, with no way to override it simply that I know of) just to make the shaders display anything at all.

It can be worked around somewhat reasonably in case of Control - using ColorRect miraculously fixes the display (although, arguably, one might not need that additional coloring in their shader at all, for a custom shader). For Node2D however, the best workaround I was able to find is to use MeshInstance2D with QuadMesh, which is way more complex than I'd expect it to be for such a simple case.


Proposed possible solutions:

Steps to reproduce

Minimal reproduction project (MRP)

N/A (see above)

Calinou commented 9 months ago
  • amend the docs to make this caveat/behaviour explicit (i.e. what is the suggested/simplest/most effective way to have Texture2D-less ShaderMaterial applied for 2D gfx), because what we currently have there doesn't apply to this case

I think ColorRect is the way to go here. It's been commonly accepted to be the quickest solution to the problem described here.

FyiurAmron commented 9 months ago

@Calinou what's with the Node2D-based case though? Would MeshInstance2D + QuadMesh be the "official" way to go, or is there a simpler solution?

Calinou commented 9 months ago

Would MeshInstance2D + QuadMesh be the "official" way to go, or is there a simpler solution?

Sprite2D + GradientTexture2D is likely faster, since rendering a sprite (which is always a rect) is faster than a polygon or mesh. If you remove one of the points from the GradientTexture2D, you get a plain-color texture.