Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flickering sprite bleed on top row of moving sprite2ds and animatedsprites in Godot V4 (does not appear in V3) #67164

Closed
Tracked by #86837
Proggle opened this issue Oct 9, 2022 · 21 comments · Fixed by #97260

Comments

@Proggle
Copy link
Contributor

Proggle commented Oct 9, 2022

Godot version

v4.0.beta2.official [f8745f2]
and
v4.0.beta4.official [e675154]

System information

Windows 10

Issue description

Godot 4 has introduced additional sprite bleed that wasn't present in V3. Example: the white line appearing above these sprites.

The one on the left is an animatedSprite, the one on the right is a Sprite2D

sprite_frames_v4b2.DEBUG.2022-10-09.18-46-16.mp4

This appears regardless of whether or not the 'fix alpha border' checkmark is enabled, and appears at seemingly random times. However, it happens on both sprites simultaneously.

It also seems to only bleed above the sprites, and not in the other 3 directions. Since it only appears for 1 frame, my video isn't capturing every appearance - it happens both when they are moving upwards and moving downwards.

Steps to reproduce

Move a sprite around, eventually you will notice flickering bleed.

Minimal reproduction project

sprite_bleed.zip

@clayjohn
Copy link
Member

I ran the MRP but was unable to reproduce the issue with default settings. I could however reproduce the issue by setting the texture filtering mode to "linear".

Did you verify that all the settings are the same between the MRP and your Godot 3 project? Importantly repear and filtering are now properties of Node2Ds rather than import settings on the sprite, so you need to ensure that they are the same. Especially if you were relying on the "pixel art 2D" texture preset

@Proggle
Copy link
Contributor Author

Proggle commented Oct 13, 2022

Running the MRP on my computer consistently produces this behavior. I just re-downloaded the zip and opened it in v4.0.beta2.official [f8745f2]

and am still seeing it, even with the settings on 'nearest'.

image

video doesn't capture all the flickers but it gets one right at the beginning.

sprite_frames_v4b2.DEBUG.2022-10-12.22-23-17.mp4

I first noticed this behavior porting a more complicated project from V3, but the MRP itself is V4 native.

@clayjohn
Copy link
Member

In your Godot 3.x project are you have the rendering/2d/snapping/use_gpu_pixel_snap project setting enabled by any chance?

@Proggle
Copy link
Contributor Author

Proggle commented Oct 18, 2022

In your Godot 3.x project are you have the rendering/2d/snapping/use_gpu_pixel_snap project setting enabled by any chance?

Not that I know of. Here's a fresh godot3 project I made, recreating the minimal godot 4 project. It works without flicker.

SpritebleedG3.zip

(It also has the godot icon in frame, so screenshots can be differentiated). I'd attach a video, but a video of the sprites moving up and down without flickering isn't that interesting.

@Proggle Proggle changed the title Flickering sprite bleed on top row of moving sprite2ds and animatedsprites in Godot V4B2 (does not appear in V3) Flickering sprite bleed on top row of moving sprite2ds and animatedsprites in Godot V4B4 (does not appear in V3) Nov 6, 2022
@Proggle
Copy link
Contributor Author

Proggle commented Nov 6, 2022

Just as an update, I've tested this with Beta 4 and it still happens.

SpritebleedG3_UpdateToBeta4.zip
For this test, instead of making a project in the beta from scratch, I imported the G3 MRP project to G4B4. I then changed filter mode to 'nearest neighbor', since that always needs to be done.

@clayjohn clayjohn modified the milestones: 4.0, 4.x Jan 14, 2023
@Proggle Proggle changed the title Flickering sprite bleed on top row of moving sprite2ds and animatedsprites in Godot V4B4 (does not appear in V3) Flickering sprite bleed on top row of moving sprite2ds and animatedsprites in Godot V4 (does not appear in V3) Mar 1, 2023
@Proggle
Copy link
Contributor Author

Proggle commented Mar 1, 2023

Still happening in the 4.0 release version.

@Calinou
Copy link
Member

Calinou commented Mar 1, 2023

I can confirm this on 4.0.rc 8208060 (Linux, GeForce RTX 4090 with NVIDIA 525.89.02):

simplescreenrecorder-2023-03-01_19.10.45.mp4

This occurs with both Forward+ and Compatibility rendering methods.

However, I'm not convinced this is a bug. The atlas sprite has a white background, which is bound to create issues whenever a texture is not perfectly aligned with the camera:
block_characters

If you replace white with transparency, the issue goes away entirely.

PS: Even if you don't replace white with transparency, enabling Snap 2D Transforms To Pixel and Snap 2D Vertices To Pixel seems to fix the issue too. (Enabling either of those may work too, but I've only tested each combination for a minute or so.)

@Proggle
Copy link
Contributor Author

Proggle commented Mar 1, 2023

However, I'm not convinced this is a bug. The atlas sprite has a white background, which is bound to create issues whenever a texture is not perfectly aligned with the camera:

It absolutely is a bug. Godot is being asked to render some specific pixels and it's putting the wrong ones on screen. Sprite bleed might be a common problem to encounter when developing an engine (I spent several weeks wrangling a bug to get Pixi rendering in JS to avoid bleeding while resizing), but it's by no means impossible to avoid (after all, Godot 3 didn't have these sprite bleed issues).

Putting a 1 pixel border around every edge of every sprite in an atlas as a workaround is a bad idea - it makes everything involving tiles and spritesheets WAY harder to work with, especially when editing them in an image program. Because of that (and to minimize file size), most assets packs and tilesets which are available for game use are designed with the assumption that the engine will display pixels correctly. If godot is going to be dev-friendly, it shouldn't require developers to go into all their assets and do additional processing by hand to work around an engine bug. If adding 2-pixel-wide gutters around every frame is necessary for the engine, that should be done programmatically by the engine itself - but since godot 3 doesn't have this issue, it's probably just a bug that can be fixed more elegantly.

EDIT: Anyway, I went back to v4 alpha 1 and it's present there, so that narrows down what changes were responsible a little bit. Gonna try titrating versions a bit further.

@Proggle
Copy link
Contributor Author

Proggle commented Mar 2, 2023

sprite_frames_v4b2.zip
Well, have struck out on finding a version without the bug, looks like it was probably part of the big render rewrite. On the plus side, I've managed to capture a static version of the sprite bleed, in the editor view, so it should be easier to figure out what's going on.

It appears that at this zoom level and location, sprite2d and animatedsprite objects at y= 76 show the sprite bleed, regardless of x coordinate. Zooming out makes the bleed disappear, returning to this zoom restore the bleed. Resizing the window or going fullscreen have the bleed persist as well.

image

moving the window around, it's possible to get different objects to bleed, and even to get bleed on the left-hand side
image
image

Hopefully that will make it easier to diagnose

@Freedomax
Copy link

I can reproduce this issue in version 4.1.1 as well. However, I know how to fix it. Just shrink each direction of the source rectangle by 0.01 pixels in width and height during rendering. That is,
RenderRect = InflateRect(SourceRect, -0.01, -0.01)

Manually setting the Sprite2D can also fix this problem, but it doesn't work for AnimatedSprite2D since it only accepts integer rectangle coordinates.
fix

@Mano-East
Copy link

Using the latest build: Issue is still there. I fixed it by surrounding every sprite with transparency, resulting in a bigger file size but no bleeding anymore.

Dumb problems require dumb solutions I guess. Crazy for an engine like this that gets celebrated alot by the pixel art community.

image

@miv391
Copy link
Contributor

miv391 commented Sep 21, 2023

Tested with v4.2.dev5.official [e3e2528].

I added green lines to the left and bottom edge. So if everything would work as expected, you would never see white lines and always green lines at left and botton edges. This happens when the sprites are positioned at *.5 pixels. The sprites don't need to move.

kuva

Left and top edges start to bleed and bottom edge is cut. If the position is e.g. *.1, this doesn't happen.

As Calinou said, setting either "Snap 2d Transforms to Pixel" or "Snap 2D Vertices to Pixel" on seems to fix this.

It's actually very easy to stumble upon this bug:

  1. create new project
  2. add Sprite2D with icon.svg
  3. add code:
extends Sprite2D

func _process(delta: float) -> void:
	position += Vector2(5, 5) * delta

The icon moves smoothly except for the flickering edges. Editing the svg file and adding empty space around the icon removes the flickering.

It is unfortunate that this is probably one of the most common first Godot programs new Godot users create.

@sepTN
Copy link
Contributor

sepTN commented Sep 22, 2023

So I just got hit by this weird bug where my slime showing sprite bleeding on the top of the image. So I just leave it at that because it was minor.

But today when I rearranged the slime in my room scene, I turned my grid snap on again (8px by 8px)
image

The bleeding is now gone, I'm not sure what happened.

@NibblyPig
Copy link

This is possibly a related issue to #81998 where it seems to be bleeding part of the spritesheet itself despite being imported as individual frames.

@Minoxs
Copy link

Minoxs commented Sep 29, 2023

As described in #81998, an AtlasTexture can be used to fix this issue without requiring modifying the spritesheet itself. If intended, it's not super intuitive and should be documented better, especially for anyone porting from v3.

@Proggle
Copy link
Contributor Author

Proggle commented Jan 20, 2024

In the latest version vertex snapping is a workaround, but given that this is basic behavior for the Sprite2D node, I feel like the default behavior out of the box should be correct. Perhaps sprites should always be vertex snapped?

@Proggle
Copy link
Contributor Author

Proggle commented Mar 7, 2024

Update since more rendering tweaks have been done, it appears that the getting-depreciated snap-to-vertex isn't 100% consistent, but is still way more reliable than snap-to-transform.

I grabbed a build of this PR here:
#88963
and with snap_2d_transforms_to_pixel enabled, I'm still getting bleed
image
Updated build with bleeding sprites visible in editor
sprite_frames_PR_88963_snap_transforms.zip

I tried to get it to bleed by moving sprites around with snap-to-vertex enabled instead, and it's considerably more difficult but still possible
image

sprite_frames_PR_88963_snap_vertexes.zip

Note that enabling both doesn't seem to solve it
image

@patowen
Copy link
Contributor

patowen commented Aug 24, 2024

As someone who got hit by this bug in a game I was helping to work on, I wanted to try to find the root cause of this bug. I did find one of the possible causes but it's weirder than I anticipated.

I would have assumed that if you were rendering a triangle in OpenGL (or any other 3D graphics library), any interpolated values passed between shaders would be within the range of the values being interpolated. In other words, I would have assumed that if a triangle's vertices had texture coordinates of (0, 0), (1, 0), and (1, 1), then every fragment's coordinates would all be in the range [0, 1]. Based on some StackOverflow posts (Example: https://stackoverflow.com/questions/63893458/msaa-and-vertex-interpolation-cause-out-of-range-values), it seems like there's a "centroid" qualifier that exists to hopefully guarantee things like this even when multisampling is enabled.

However, with a very simple OpenGL test program I set up on my machine, on a 512x512 viewport, rendering a single triangle with vertex coordinates at (-0.5, -0.5019531), (0.5, -0.5019531), (0.5, 0.0) and texture coordinates (0, 0), (1, 0), (1, 1), a vertex shader of

#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTex;
out vec2 vTex;
void main()
{
   vTex = aTex;
   gl_Position = vec4(aPos, 0.0, 1.0);
}

and a fragment shader of

#version 330 core
out vec4 fragColor;
in vec2 vTex;
void main()
{
	if (vTex.y == -0.000000059364538) { // Exact value found with a lot of trial and error
		fragColor = vec4(1, 0, 0, 1);
	}
	else {
		fragColor = vec4(0, 0, 0, 1);
	}
}

I get a triangle with a red seam at the the bottom, showing that vTex indeed becomes negative despite the vertex shader always outputting values in the [0, 1] range.

The exact behavior seems to depend heavily on the setup, as I wasn't able to reproduce the same issue with the same setup in WebGL on the same machine, nor was I able to reproduce it on a different machine (with integrated graphics).

Due to this sensitivity, I'll mention that i ran these tests on a "NVIDIA GeForce GTX 980 Ti" with a driver provided by NVIDIA with driver version 31.0.15.3623 (June 8 2023).

I'm not that familiar with OpenGL/Vulkan/etc. specs, so I don't know whether this is a driver bug or whether it's acceptable according to the specs. Regardless, it seems like the only way to prevent a bug like this for sure without adding any fudge factors to the part of the texture being sampled would be to prevent the edges of any quads produced by this Sprite2D renderer from getting too close to the center of a pixel. When scaling and rotation is involved, this seems infeasible.

Based on my experiments with a 512x512 viewport, the bug occurs every time a sprite is within about 0.002 units of a half-pixel. In other words, if a sprite is rendered at a random location, there would be a 1/250 chance for it to be close enough to the half-way point between pixels for this bug to occur.

In my own opinion, the fix for this would probably be to bake Freedomax's suggestion (#67164 (comment)) into the engine. I'm hopeful that the (default) fudge factor can be very small, on the order of 1e-5 pixels, to the point where it would be unnoticeable.

EDIT: It should be possible to fix this without a fudge factor by clamping the texture UV in the fragment shader. This seems to be done already when FLAGS_CLIP_RECT_UV is enabled, but that isn't enabled by default as far as I can tell, which is odd, as it's hard for me to imagine a reason to disable it. If this fix is preferred, I believe we would also need to be careful to ensure that the computation of what to clamp to takes floating point precision into account.

@NibblyPig
Copy link

Personally I just exported my sprites and added a 1px transparent border. It's not ideal as it messes with autodetecting the sprite size/collisions but it seems to be the simplest resolution.

@mharmcode
Copy link

Similar to NibbyPig, I discovered an 'Inner Padding' option when exporting sprite sheets in Aseprite, which serves as a temporary workaround (for Asesprite users).

AsepriteInnerPaddingOption

@clayjohn
Copy link
Member

I have a tentative fix for this issue! If you are able to build locally, please test #97260 and confirm if it fixes the issue for you.

I was only able to reproduce the problem using the newest MRPs from Proggle here #67164 (comment), but testing with those I can confirm the issue goes away entirely when snap_2d_vertices_to_pixel is enabled with the build from #97260

@akien-mga akien-mga modified the milestones: 4.x, 4.4 Sep 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.