Closed MeanRaccoon closed 1 month ago
Please test this on 4.2.1 to make sure the issue is still present in the latest Godot version.
region_filter_clip_enabled
is only used a single time in the Sprite2D code (excluding getter/setter and binding): https://github.com/godotengine/godot/blob/10e111477db68fe65776a1d68fb1ffccaf6520fc/scene/2d/sprite_2d.cpp#L82
Here's the testing in v4.2.1. To my eyes, the results are the same as for v4.1.1
It's... weird. Very lacking class reference description aside, all that property does is clip the UVs. You did understand what it's supposed to do, and yet it does not seem to work.
The same happens with the equivalent draw_*
method. Changing the last parameter clip_uv
does not affect anything with linear filtering.
draw_texture_rect_region(preload("res://sokoban_spritesheet.png"),
Rect2(32, 32, 64, 64), Rect2(256, 256, 64, 64), Color.AQUA, false, true)
I think this should be classified as a bug. Something is not right here.
I don't know yet enough C++ to dig into the details of the source code, but it seems this feature has indeed not been developed. Not even in Godot 3.x I was able to obtain any result whatsoever by doing that experiment.
The way texture_filter
was used in Godot 3.x looks different because the GUI has changed significantly. The user had to change the texture filter from the Import tab (unlike in Godot 4.x, which is done in the inspector window)
Also the concept of "nearest" for texture filter didn't seem to be very popular. This is what the docs look like for v3.5.3 and v4.2.1:
Last thing I'd like to add in this reply is that it seems simple to avoid pixel bleeding: by working with a nicely built spritesheet, with transparency and enough space between the individual sprites. These are my impressions so far.
FWIW, I'm also experiencing the same with AtlasTexture
's filter_clip
property. On or off, it doesn't seem to make any difference. :confused:
v4.2.1.stable.official [b09f793f5]
I've seen enough to classify this omission as a bug at this point.
CC @clayjohn you may know something no one does
I've digged a little and here's a brief history of this property:
It was added in https://github.com/godotengine/godot/commit/0288be1e7698c984b159bbb7816ddc7aca43b2f1. Initially it was shrinking the sampled region by half texel (on each side): https://github.com/godotengine/godot/blob/0288be1e7698c984b159bbb7816ddc7aca43b2f1/drivers/gles3/shaders/canvas.glsl#L248-L252
Such shrinking of the sampled region resulted in https://github.com/godotengine/godot/issues/11417.
Which were fixed by https://github.com/godotengine/godot/pull/12828, which removed the shrinking whatsoever, so UV were clamped directly/exactly to the region: https://github.com/godotengine/godot/blob/6adaf8f47a9acbb8d56fa60e47a1106e5646c299/drivers/gles3/shaders/canvas.glsl#L382-L386
Currently it's still like that: https://github.com/godotengine/godot/blob/68ad520da4365c866ceea42e0238b2ea24647289/drivers/gles3/shaders/canvas.glsl#L580-L582 https://github.com/godotengine/godot/blob/68ad520da4365c866ceea42e0238b2ea24647289/servers/rendering/renderer_rd/shaders/canvas.glsl#L488-L490
Clamping UV like that doesn't affect how the texture sampling is done, filtered sampling still can sample from outside of such region, hence the bleeding.
I'd guess shrinking the region which UV are clipped to by some arbitrary threshold (less than the 0.5
texel used in the original implementation) is not a viable solution, as it will probably also introduce some artifacts depending on the scale/zoom/etc.?
The best would be to prevent filtered sampling from sampling outside of such region, but I don't know if this is feasible at all.
And if there's no way to solve this reliably then maybe it should be considered to deprecate this clipping / relevant properties. AFAICT currently it does pretty much nothing (UV are already based on the source rect in case of using region so in theory it doesn't change anything; unless it prevents some floating point issue? :thinking:).
This needs to be assessed by the rendering team. @godotengine/rendering
Thanks for doing a deep dive into this I think https://github.com/godotengine/godot/pull/12828 was an incorrect fix. The way I see it, you described two distinct problems:
region_filter_clip_enabled
should remove that bleed.region_filter_clip_enabled
pixelated textures using nearest filtering stretch at the edgesTo me this indicates that we should restore the half pixel offset (might only need to be a quarter pixel offset) when using linear filtering and remove it with nearest filtering. The trouble with that is that filtering can vary based on the texture itself. In custom shaders this setting becomes a little bit dangerous.
Some background, this code is only ever used when rendering rects or ninepatchs. which means the UV is always calculated by us in the vertex shader: https://github.com/godotengine/godot/blob/68ad520da4365c866ceea42e0238b2ea24647289/drivers/gles3/shaders/canvas.glsl#L216
Then interpolated for each fragment before clipping can happen. Errors can arise in two ways:
src_rect
might be off:src_rect
is calculated in floating point and may point at a sub pixel position in the first place.
Accordingly I would do the following:
src_rec
to ensure the sample point always aligns with the source texel)Any news on this subject ? Thanks a lot
Thanks for doing a deep dive into this I think #12828 was an incorrect fix.
To me this indicates that we should restore the half pixel offset (might only need to be a quarter pixel offset) when using linear filtering and remove it with nearest filtering. The trouble with that is that filtering can vary based on the texture itself. In custom shaders this setting becomes a little bit dangerous.
After looking into this, I agree that #12828 was likely an incorrect fix. The original code is
uv = clamp(uv,src_rect.xy+half_texpixel,src_rect.xy+abs(src_rect.zw)-color_texpixel_size);
The intent of this code was to clamp the UV coordinates to the centers of the outermost texels, which achieves the desired effect of avoiding sprite bleeding caused by linear interpolation. No noticeable precision issues can result from this, as we are not close to any sharp boundaries.
The problem is that the code subtracts by color_texpixel_size
at the end instead of half_texpixel
, which is an easy mistake to make due to the habit of thinking of rectangles in terms of position and size instead of min and max coordinates.
However, the fix in #12828 removed all of the offset, defeating the original purpose of the option.
I believe that the following change to servers/rendering/rendering_rd/shaders/canvas.glsl (and a corresponding change for GLES) should fix "region_filter_clip_enabled" entirely:
#endif
if (bool(draw_data.flags & FLAGS_CLIP_RECT_UV)) {
- uv = clamp(uv, draw_data.src_rect.xy, draw_data.src_rect.xy + abs(draw_data.src_rect.zw));
+ vec2 half_texpixel = draw_data.color_texture_pixel_size * 0.5;
+ uv = clamp(uv, draw_data.src_rect.xy + half_texpixel, draw_data.src_rect.xy + abs(draw_data.src_rect.zw) - half_texpixel);
}
#endif
I tested this on my machine, and it worked. It doesn't matter whether "nearest" filtering or "linear" filtering are used, as it produces the correct results in both cases. It also shouldn't matter what transformation is applied to the sprite, so I am hopeful that this can be a clean and simple fix.
Avoiding sprite bleed for "Nearest" filtering without filter clip enabled is another matter that is likely also worth addressing, but I'm not sure whether that's on topic for this particular issue. I've put my notes regarding that issue in https://github.com/godotengine/godot-proposals/discussions/10846, but I don't think there's as clean a fix.
Tested versions
Reproducible in (please see screenshots)
System information
Godot v4.1.1.stable.mono - Windows 10.0.19045 - Vulkan (Compatibility) - Intel(R) HD Graphics 630 (Intel Corporation; 27.20.100.8280) - Intel(R) Core(TM) i5-7400 CPU @ 3.00GHz (4 Threads)
Issue description
I've been testing and studying in detail Sprite2D node, its capabilities, methods and properties. I am questioning the usefulness of the boolean property
region_filter_clip_enabled
because I've been unable so far to make it work how I imagined it should work, despite all my testings. I understand also this subject has to do with the concept of "pixel bleeding". According to the docs:_If true, the outermost pixels get blurred out.
region_enabled
must be true._By "outermost pixels", I believe we're talking about the pixels at the border of the selected rectangular region, though the thickness of this border is not specified (1 pixel, 2 pixels witdh...?).
I've been discussing the issue in Godot's communities, and I've been taught that spritesheets with tighly grouped sprites could lead to pixel bleeding. With this set up, I could start testing out the functionality of the aforementioned property. When set to true, I expected this property (at least to a certain degree) to mitigate the pixel bleeding that arises when the CanvasItem
texture_filter
is set to any of its linear variations (linear, linear mipmap, linear mipmap anisotropic), but I see no difference at all, which leads to suspect if this property is working properly, or if its been deprecated long ago and now is rubble.Meanwhile, when
texture_filter
is set to any of its "nearest" variations, no bleeding occurs regardless of the value ofregion_filter_clip_enabled
(the preferred case for pixel art).Steps to reproduce
All these screenshots were made in Godot 4.1.1. To test this out, I used this classic Sokoban spritsheet from Kenney's assets, where the boxes sprites have no spacing at all between them:
Carefully select the region for this blue box:
Try out many combinations of
region_filter_clip_enabled
andtexture_filter
properties. (Not alltexture_filter
are shown in this screenshot because the visual result repeats itsel,f as explained in the issue description above):Minimal reproduction project (MRP)
Sprite2D region_filter_clip_enabled test.zip