godotengine / godotengine/godot
Using Texture2D-less (e.g. Texture2DArray-based) ShaderMaterial for CanvasItem subclasses problematic
- Dominant language
- C++
- Stars
- 117k
- Forks
- 26.8k
- PR merge metrics
- PR metrics pending
Description
### Tested versions
- Reproducible in: v4.2.1.stable.mono.official [b09f793f5]
### 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](https://docs.godotengine.org/en/stable/tutorials/shaders/your_first_shader/your_first_2d_shader.html) say this:
>To begin, create a Sprite2D node. [You can use any CanvasItem](https://docs.godotengine.org/en/stable/tutorials/2d/custom_drawing_in_2d.html#doc-custom-drawing-in-2d), 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:
* create either Control (or its subclass dedicated for this particular case) or Node2D (or its subclass dedicated for this particular case)
* in case of Control (or its subclass) provide the usual Layout -> Custom Minimum Size & ->Transform (Size & Position)
* in case of Node2D (or likely its subclass), provide the Transform -> Position _and something else_ (no way to provide the size here directly though Node2D ATM, but that's reasonable)
* assign the shader material to it, configure it etc.
* *it works!*
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:
* allow the base Control class objects to display ShaderMaterials the way ColorRect does, minus obviously the tinting (which would also probably make the ShaderMaterial work for all child classes as well out-of-the-box, currently it works only for some of them, like `ColorRect`) - I see no reason why this shouldn't work by itself, as those objects all have well-defined rect to apply the shader to; I guess it's the effect of "so long as it is drawing to the canvas" condition... but why wouldn't base Control just draw, especially if I assign a custom shader to it??
* consider having a simple Rect2D or similar Node2D child that would just handle the simplest case of 2D rect
* amend the docs to make the this caveat and intended user 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 in the tutorial doesn't apply to this case, and we have no further info on this (at least I wasn't able to find it)
* probably have the above info linked somewhere under https://docs.godotengine.org/en/stable/classes/class_texture2darray.html - especially taking into account that the example shader code from https://docs.godotengine.org/en/3.5/classes/class_texturearray.html disappeared from 4.x docs, I'd say that example code should be migrated, alongside the link to the new info
### Steps to reproduce
* Create any simple `shader_type canvas_item;`-type shader
* Use it to create a ShaderMaterial
* Imagine being a first-time user and then try to find a class in both Control or Node2D inheritance tree that would be applicable to just display the shader on a 2D polygon without any additional texture applied etc. , e.g. to be able to use Texture2DArray or similar, using the editor and official docs and official tutorials available for 4.x
### Minimal reproduction project (MRP)
N/A (see above)
Contributor guide
Research direction
Start with the linked 2D shader tutorial and the Texture2DArray documentation, then reproduce the behavior using Control, Node2D, ColorRect, Sprite2D, TextureRect, and MeshInstance2D with a canvas_item shader. Decide whether the scope is an engine change, a dedicated 2D rectangle node, or documentation, and define completion as a clear supported workflow for displaying a textureless shader on a rectangle.
Written by the indexing model from the issue text.
Assessment
- Domain
- computer-graphics
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100