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

2D Sprite "jittering", very noticeable when using camera smoothing #35606

Closed
forbjok opened this issue Jan 27, 2020 · 60 comments
Closed

2D Sprite "jittering", very noticeable when using camera smoothing #35606

forbjok opened this issue Jan 27, 2020 · 60 comments

Comments

@forbjok
Copy link

forbjok commented Jan 27, 2020

Godot version:
3.1.2 stable, GitHub master (3.2), Vulkan branch (4.0)

OS/device including version:
Windows 10 x64 v1909

Issue description:
Pretty much as soon as I made my test level larger than a single screen, I noticed something was off. After a bit of testing, I've found out what it is. It seems like any time the camera moves, there is some sort of inconsistency in the positions sprites are being rendered at, causing them to "jitter" back and forth by one pixel even though the camera is only moving in one direction.

Example gifs showing the issue:
A5aCVeO
EhPwu5N

How noticeable it is depends on circumstances. Generally, from what I've found, it's most noticeable if you have camera smoothing turned on and the smoothing speed set to a very low value, but the problem isn't related to the camera smoothing. It's also present with camera smoothing turned off, just harder to notice.

In case it was a bug that had already been fixed, I tried compiling Godot myself from both the master (presumably what will become 3.2) and vulkan (4.0?) branches to try them out, but unfortunately the problem still seems to be present in both of those. It looks a bit different in the Vulkan branch, due to what I assume is some sort of smoothing/blur filter being applied (there may be a way to turn that off, but I couldn't find it) to the background, but it is definitely still there.

Steps to reproduce:

  1. Start minimal reproduction project
  2. Run far enough to the right that the camera starts scrolling and stop.
  3. Watch the vertical lines of the background brick wall or the ground near the player to see the sprite jittering back and forth by one pixel until the camera fully stops

Minimal reproduction project:
godot-jitter-repro-project.zip

@Seel
Copy link

Seel commented Jan 27, 2020

Seems to me like the camera itself isn't respecting the pixel snap setting.

@forbjok
Copy link
Author

forbjok commented Jan 27, 2020

The issue happens regardless of whether Pixel Snap is enabled or not.
If the issue was caused by the camera, shouldn't everything be jittering?
As far as I can tell, it's only the sprites that jitter. The background tilemap and text label does not.

@Seel
Copy link

Seel commented Jan 27, 2020

The issue happens regardless of whether Pixel Snap is enabled or not.
If the issue was caused by the camera, shouldn't everything be jittering?
As far as I can tell, it's only the sprites that jitter. The background tilemap and text label does not.

The tilemap is one cohesive unit so it does jitter, but individual tiles within might not. I tried this right now with multiple tilemap layers, background and foreground, and that way you can really see them jitter too.

Since pixel snap doesn't seem to affect the camera, it's position.x can be something like 10.375940 and relative to this position one object that's pixel snapped might get it's position rounded up to the nearest pixel, while another one's gets rounded down.

At least that's my interpretation of what's happening.

@forbjok
Copy link
Author

forbjok commented Jan 27, 2020

The tilemap is one cohesive unit so it does jitter, but individual tiles within might not.

I tried looking at the edge of the screen during scrolling, and I was not able to spot any jittering on the background itself relative to the edge. Shouldn't it be possible to see it jitter if comparing it to the edge?

In my example project the background and foreground actually are two separate tilemaps (although they use the same tileset), and I've never been able to spot any jittering between them.

Maybe pixel snap works differently than I thought, but I would have thought that if this was caused by pixel snapping, it would stop happening when Pixel Snap was turned off?

it's position.x can be something like 10.375940 and relative to this position one object that's pixel snapped might get it's position rounded up to the nearest pixel, while another one's gets rounded down.

My first thought was also that it was caused by some sort of weird inconsistent rounding of pixel coordinates. Basically, some sort of rounding being applied somewhere that's either RNG or just inconsistent in that sometimes higher values get rounded down instead of up. I couldn't find anything obvious when poking around in the source code though.

@Seel
Copy link

Seel commented Jan 27, 2020

Not saying pixel snap is the cause, rather it doesn't really do anything.

pixeltest-isodemo.zip

I messed with the 2D isometric demo a bit, you can really see the jitter between walls and pillars and the floor tiles in this.

@forbjok
Copy link
Author

forbjok commented Jan 27, 2020

Ah, yes. There's definitely some jittering going on there. I wonder why I'm only seeing it with the sprites in my game.

@lawnjelly
Copy link
Member

I've not done any 2D stuff with Godot but it looks like the problem is something like this : your character sprite position is not snapped relative to your background.

i.e. if snapping is every 1.0 in units, and your background is at position 4.0, and your sprite is at 6.7:

At camera position 2.0

(assuming we are using a floor function to snap, but same will happen with round)
background = 4.0 - 2.0 = 2.0 = 2
sprite = 6.7 - 2.0 = 4.7 = 4

at camera position 1.6

background = 4.0 - 1.6 = 2.4 = 2
sprite = 6.7 - 1.6 = 5.1 = 5

Thus as the camera moves you are getting relative movement between the two by 1 pixel. The solution is to snap the background and the sprite positions relative to each other.

@Seel
Copy link

Seel commented Jan 27, 2020

Thus as the camera moves you are getting relative movement between the two by 1 pixel. The solution is to snap the background and the sprite positions relative to each other.

Pixel snap should be handling this, shouldn't it?
And it does, if you have an integer camera position, but since the camera is not snapped you get unpredictable results.

If for example you put this in the camera's _process function:
position = player.position.round()
force_update_scroll()
Everything works and there's no jitter with this, but you cannot use the camera node's smoothing function.

@forbjok
Copy link
Author

forbjok commented Jan 27, 2020

I tried manually .floor()-ing the positions of the camera and sprite in various gdscript handlers yesterday, and so far I haven't found anything that actually fixes the jitter. Maybe I could try implementing a custom camera that just sets the underlying render position directly (pretty sure I saw some way to do that somewhere, but I forget what it's called) and .floor()s it. Will definitely try that later.

If that fixes it, this kinda seems like something that should just be done automatically by default. I can't think of any reason you'd ever actually want objects arbitrarily jittering relative to each other during scrolling.

@Seel
Copy link

Seel commented Jan 27, 2020

I tried manually .floor()-ing the positions of the camera and sprite in various gdscript handlers yesterday, and so far I haven't found anything that actually fixes the jitter.

Did you try without camera smoothing enabled? Otherwise the smoothing happens after your script and turns the camera position into a float again.

my scene tree is ordered like this:

player
    sprite
    collision shape etc.
camera

This way the camera gets the player position after the player moved.

onready var player = get_node("../Player")

func _process(delta):
	position = player.position.round()
	force_update_scroll()

This should work.

@lawnjelly
Copy link
Member

Ok I downloaded your demo project and got it working:

  1. In your player scene, create a new Node2D and call it Player
  2. Make this new Node2D the scene root (so that the kinematic thing is a child of it)
  3. Move the Sprite to be a child of this root node.
  4. Change the
onready var sprite: = get_node("Sprite")

to

onready var sprite: = get_node("../Sprite")
  1. In _physics_process at the end add:
	sprite.position = Vector2(floor(position.x), floor(position.y))

Obviously you'll want to tweak it a bit but the general idea is this .. to separate the physics representation (kinematic) from the visual representation. The physics rep can have float coordinates, but the visual representation should be floored or rounded to whole numbers.

With this working, there will always be an integer relative relationship between the background and the sprites, and you won't get this jitter.

@forbjok
Copy link
Author

forbjok commented Jan 27, 2020

Did you try without camera smoothing enabled?

I don't remember if I tried rounding the camera position with smoothing turned off, but the jitter is present without smoothing. It's just more noticeable when smoothing is enabled.

This should work.

I'll try that when I have the opportunity, although I'll probably still have to either implement a custom camera or just implement custom smoothing on the existing one.

Ok I downloaded your demo project and got it working:

  1. In your player scene, create a new Node2D and call it Player
  2. Make this new Node2D the scene root (so that the kinematic thing is a child of it)
  3. Move the Sprite to be a child of this root node.
  4. Change the
onready var sprite: = get_node("Sprite")

to

onready var sprite: = get_node("../Sprite")
  1. In _physics_process at the end add:
	sprite.position = Vector2(floor(position.x), floor(position.y))

Obviously you'll want to tweak it a bit but the general idea is this .. to separate the physics representation (kinematic) from the visual representation. The physics rep can have float coordinates, but the visual representation should be floored or rounded to whole numbers.

With this working, there will always be an integer relative relationship between the background and the sprites, and you won't get this jitter.

I'll try this as well when I have the opportunity, but wouldn't rounding the position set to the viewport's canvas_transform (I believe this is what Camera2D sets internally?) also fix the issue?

@lawnjelly
Copy link
Member

I'll try this as well when I have the opportunity, but wouldn't rounding the position set to the viewport's canvas_transform (I believe this is what Camera2D sets internally?) also fix the issue?

I'm not sure exactly what you mean, but it may be possible to achieve the same math effect via a slightly different mechanism (again, I am not familiar with 2D). But once you have a moving camera, snapping positions to pixels on the screen no longer works because you will get this jiggling, you need to snap relative to the SCENE.

@golddotasksquestions
Copy link

I don't use Camera smoothing with pixelart for that reason. Which is a shame, because if it wasn't for this issue, there would be no reason not to use smoothing.

@forbjok
Copy link
Author

forbjok commented Jan 27, 2020

I tried manually .floor()-ing the positions of the camera and sprite in various gdscript handlers yesterday, and so far I haven't found anything that actually fixes the jitter.

Did you try without camera smoothing enabled? Otherwise the smoothing happens after your script and turns the camera position into a float again.

my scene tree is ordered like this:

player
    sprite
    collision shape etc.
camera

This way the camera gets the player position after the player moved.

onready var player = get_node("../Player")

func _process(delta):
	position = player.position.round()
	force_update_scroll()

This should work.

I can confirm that this fixes the jitter, although it makes the built-in smoothing unusable, so I'll have to reimplement that manually.

Ok I downloaded your demo project and got it working:

  1. In your player scene, create a new Node2D and call it Player
  2. Make this new Node2D the scene root (so that the kinematic thing is a child of it)
  3. Move the Sprite to be a child of this root node.
  4. Change the
onready var sprite: = get_node("Sprite")

to

onready var sprite: = get_node("../Sprite")
  1. In _physics_process at the end add:
	sprite.position = Vector2(floor(position.x), floor(position.y))

Obviously you'll want to tweak it a bit but the general idea is this .. to separate the physics representation (kinematic) from the visual representation. The physics rep can have float coordinates, but the visual representation should be floored or rounded to whole numbers.

With this working, there will always be an integer relative relationship between the background and the sprites, and you won't get this jitter.

This does fix the jitter for the player, but would have to be implemented separately for every sprite, making it rather impractical.

Ultimately, this is the solution (or workaround) I arrived at:

extends Camera2D

# Smoothing duration in seconds
const SMOOTHING_DURATION: = 0.2

# The node to follow
var target: Node2D = null

# Current position of the camera
var current_position: Vector2

# Position the camera is moving towards
var destination_position: Vector2

func _ready() -> void:
	current_position = position

func _process(delta: float) -> void:
	destination_position = target.position
	current_position += Vector2(destination_position.x - current_position.x, destination_position.y - current_position.y) / SMOOTHING_DURATION * delta
	
	position = current_position.round()
	force_update_scroll()

It's a combination of Seel's suggestion with a custom reimplementation of camera smoothing based on one I created in a MonoGame-based engine years ago. This completely fixes the issue for all sprites without having to do anything specific in each sprite, while still having a nice smooth camera.

This still seems like something Camera2D should be doing internally by default though, as I can't imagine the current behavior ever being desirable.

@Calinou
Copy link
Member

Calinou commented Jan 27, 2020

It seems Camera2D currently doesn't have specific code to handle the Use Pixel Snap project setting. It makes sense to also snap the camera's coordinates when that setting is enabled.

Someone should try to modify scene/2d/camera_2d.cpp to snap the camera's coordinates when pixel snap is enabled and check whether the issue is still here (when Use Pixel Snap is enabled).

@lawnjelly
Copy link
Member

Agree, the simple camera snapping may be good enough in this case 👍. The snapping per sprite works with the more general case, because it allows things like variable size pixel blocks (e.g. 1 sprite pixel is 4x4 on screen), and zooming, non-snapped camera etc. But it may be overkill in this situation, and could of course be custom implemented in a game that needed this.

@forbjok
Copy link
Author

forbjok commented Jan 27, 2020

I made a pull request that rounds the Camera2D origin to nearest pixel before setting the canvas transform. This fixes the jittering without any special camera script.

Not sure if it's implemented in the most idiomatic way possible, but it seems to work at least.

@fossegutten
Copy link
Contributor

fossegutten commented Jan 28, 2020

The reason that smoothing and custom camera scripts seem to increase jitter is because camera parent node will always be aligned to camera perfectly, if smoothing is disabled. Camera parent is usually player node, so player and background will draw fine, if smoothing is disabled. Other nodes with non-rounded positions will still jitter. If pixel snap in editor is used for backgrounds / non-moving sprites, you will only see jitter for moving sprites.

@forbjok commit seems to fix the issue for all cases. Very clever indeed! I was using a gdscript workaround hack for this.

@Tuatarian
Copy link

One fix for this that worked for me, when pixel snap and such did not, was setting the Camera's update mode in the inspector from Idle to Physics. Completely removed the jitter

@fossegutten
Copy link
Contributor

fossegutten commented Aug 1, 2020

I figured out a way to fix the jitter completely from gdscript, by coding a custom camera.
Using physics mode or pixel 2D snap is not necessary at all.

I do this as a final step on my camera transform:

t.origin = t.origin.snapped(Vector2.ONE * global_scale)

To port this to the default camera, the position should be Snapped by the zoom value, instead of Vector2(1.0, 1.0) / rounding! Simple as that.

@nihiluis
Copy link

nihiluis commented Aug 5, 2020

is this the same bug?

https://godotengine.org/qa/78937/remove-render-jitter-artifacts-from-moving-camera

The strategies laid out in this issue didn't work for me.

@thejokertm
Copy link

I am also having trouble with this... unfortunately the workarounds mentioned here didn't do it for me. There is still jittering. When I implement the code solution above my player character starts vibrating, jittering in very quick succession. Any other ideas?

https://streamable.com/rq0jka
https://streamable.com/drg21f

@neilfranci
Copy link

@golddotasksquestions Yes, I created two pull requests, for 3.x and 4.x. Hopefully it'll be there for the 3.2.3 release. I'll be on 3.x for a while :-)

@forbjok I may have a present for you.

GIF 25-Aug-20 10-29-01

Hi, how you fix that problem I have the same when player stop it jitting, I dont know how to fix it at this time. v3.2.3

@m6502
Copy link

m6502 commented Oct 23, 2020

@SyliawDeV It'll be corrected when you download the next Godot version including the pull request that fixes the bug :-)

@neilfranci
Copy link

@SyliawDeV It'll be corrected when you download the next Godot version including the pull request that fixes the bug :-)

oh maybe v3.2.4 stable, I check beta version everyday but not seem it fix or not. But hear u say that I can wait for new version. :)

@Calinou
Copy link
Member

Calinou commented Oct 24, 2020

@SyliawDeV #41535 hasn't been merged yet, so it won't be in 3.2.4 if it's not merged by the time 3.2.4 is released.

@neilfranci
Copy link

@SyliawDeV #41535 hasn't been merged yet, so it won't be in 3.2.4 if it's not merged by the time 3.2.4 is released.

Hm, dont know how other people do with that.
Until I know that I will sleep in freeze.

@m6502
Copy link

m6502 commented Oct 24, 2020

@Calinou @SyliawDeV I'll try to add the required project setting and test it this weekend so it can be merged in the next revision.

@neilfranci
Copy link

@Calinou @SyliawDeV I'll try to add the required project setting and test it this weekend so it can be merged in the next revision.

You just warm me from freeze. 🤩

reduz added a commit to reduz/godot that referenced this issue Oct 30, 2020
-Rename pixel_snap to snap_2d_to_vertices
-Added snap_2d_to_transforms which is more useful

Fixes godotengine#41814
Solves proposal godotengine/godot-proposals#1666
Supersedes godotengine#35606, supersedes godotengine#41535, supersedes godotengine#41534
@akien-mga
Copy link
Member

Fixed by #43194 (4.0) and #43554 (3.2.4).

@Grizzly-Alex
Copy link

Many users have problems with camera2D lag in Godot. I spent a lot of time on project settings. Used all camera setup options. Tried to fix the delays with code. It was all unsuccessful. Godot has long had problems with this, and they are not corrected. The only thought I came up with was to use Unity. It's a pity, but until this problem is solved, it makes no sense to use the Godot engine.

@m6502
Copy link

m6502 commented Dec 30, 2021

@Grizzly-pride Before my fix was pulled there was one 3.x version released including it. You could always give it a try to see if that would fix your problems; it certainly did for me.

I'm willing to give this a try again at some point because it's a deal-breaker for too many people, myself included. I've already seen people remake projects in Unity because the customers didn't like the objects and the maps not being in sync, and that's a pity because if someone spends time to learn Godot and has to switch to Unity it's going to be difficult to convince them to take the step back because of fear of having to spend the time again for nothing. This is by far the worst issue Godot has.

@Calinou
Copy link
Member

Calinou commented Dec 30, 2021

An alternative solution for now is to avoid relying on built-in Camera2D smoothing (or only use it to a minimal amount). The kind of smoothing Camera2D performs (asymptotic smoothing) is not something that was used in 90s pixel art games anyway – assuming you're going for that kind of aesthetic 🙂
It's possible to write your own custom Camera2D smoothing with linear interpolation, which will never slow down to very low speeds that make this issue obvious.

Using the 2d stretch mode instead of the viewport stretch mode will also help make this issue less noticeable at higher resolutions.

I'm not trying to discourage anyone from resolving this issue, but I wanted to state again that workarounds do exist.

@superlazyname
Copy link

I think I ran into this in v3.4.4 on Linux. The Pixel Snap setting seemed to fix it but I don't completely understand what causes this and I'd like a bit more information if anyone can provide it.

Strangely enough it only happened with very specific sprites. I tried all of the following:

  • Moving the sprite up and down in the tree hierarchy
  • Moving the sprite around in the scene
  • Putting another sprite in the same spot where the jittering one was (it did not jitter)

It does not seem to be relat4ed to the order that sprites are added to the scene, or any pattern I was able to find.

@golddotasksquestions
Copy link

golddotasksquestions commented Jun 27, 2022

@superlazyname

Strangely enough it only happened with very specific sprites.

Maybe try comparing Sprites with odd and even number of texture side length. If that's the cause, try disabling the "centered" property of the Sprite.

@m6502
Copy link

m6502 commented Jun 27, 2022

Can't remember the exact conditions right now but if memory serves me well this bug manifests when sprites are not in exact integer positions, i.e. try setting one sprite at X = 10.1 other at X = 25.2 other at X = 40.3... Then move the camera slowly in X and you can start crying. I think camera zoom affected this, too. One 3.x version was released with my PR but unfortunately some other bugs manifested (IMHO latent bugs that were brought to light when the sprites stopped jittering) and in the end the PR was pulled. I'm still willing to try to integrate the PR again but right now I can't devote the time required to find the other bugs that manifested. I think it would be a good idea to add an option and let the user decide. Sprites just can't jitter, and, again IMHO, this issue gives Godot a very bad image. I still think this is by far the worst issue this engine has right now.

@zhengxiaoyao0716
Copy link

I found this problem still exist at the v4.2.1.stable version.

Test.DEBUG.2024-03-08.19-32-30.mp4

test_project.zip

@Calinou
Copy link
Member

Calinou commented Apr 16, 2024

I found this problem still exist at the v4.2.1.stable version.

For future reference, this occurs because the project isn't using physics interpolation. This means the player sprite is moving at a different frequency than the screen is being rendered at, which will cause visible jitter. To resolve this, you need physics interpolation. 2D physics interpolation will be available in 4.3: #88424

Until 4.3 is released, you can use https://github.com/lawnjelly/smoothing-addon (also for 3D).

Regarding the original issue, this was improved a lot in 4.3 too: #87297

@Jaso333
Copy link

Jaso333 commented May 1, 2024

I found this problem still exist at the v4.2.1.stable version.

For future reference, this occurs because the project isn't using physics interpolation. This means the player sprite is moving at a different frequency than the screen is being rendered at, which will cause visible jitter. To resolve this, you need physics interpolation. 2D physics interpolation will be available in 4.3: #88424

Until 4.3 is released, you can use https://github.com/lawnjelly/smoothing-addon (also for 3D).

Regarding the original issue, this was improved a lot in 4.3 too: #87297

I'm using a custom build with Physics Interpolation available. I have it switched on, and it is still jittering exactly as it is described in the opening post.

@Calinou
Copy link
Member

Calinou commented May 6, 2024

I'm using a custom build with Physics Interpolation available. I have it switched on, and it is still jittering exactly as it is described in the opening post.

Please open a new issue with a minimal reproduction project attached. Also, make sure the stretch scale mode is set to integer: https://docs.godotengine.org/en/stable/tutorials/rendering/multiple_resolutions.html#stretch-scale-mode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment