Godot: Sprites with subpixel world positions are displayed with unexpected and uneven displacements

Created on 24 Aug 2020  路  11Comments  路  Source: godotengine/godot

I tried finding this issue reported but I've come empty handed.

Godot version:

3.2.2 official build.

OS/device including version:

Windows, happens both with GLES2 and GLES3.

Issue description:

Creating a small test level I dragged many (parallax) map objects together without caring about perfect positioning of anything, so about all of them ended up in non integer (X, Y) positions.

When moving the camera the elements display at slightly unexpected positions, separating ones from the others even though all of them have equal parallax parameters, thus destroying the illusion of cohesion of the elements. If I erase the subpixel amounts of the coordinates everything displays as expected.

Further tests showed this behavior is not limited to parallax elements but is a more general issue that happens with regular 2D sprites too. If sprites have non integer positions the final display positions get jumpy, like if the final position is sometimes rounded up and sometimes down, depending on the relative position to the camera.

I think it's important to remark that the Pixel Snap option does nothing to prevent this behavior.

Steps to reproduce:

Draw a bunch of 2D sprites at non integer coordinates and scroll slowly to experience the issue.

Minimal reproduction project:

I'm including a small project with two cameras in split screen that slowly move in circles over a bunch of sprites. At the left the sprites have subpixel positions and experience this issue. At the right the sprites are at integer positions and all can be seem displaying as a solid block. I think it's reasonable to expect the left camera to display the static sprites always at the same positions relative to each other.

GIF 24-Aug-20 16-00-08

spritepositions.zip

bug rendering

Most helpful comment

I think the fix is good. I'm trying different sceneries and it seems to work fine for me all of the time. Here is a small capture of the example project with a very zoomed camera showing two intersecting sprites with alpha borders and subpixel positions, getting perfect relative positioning in contrast to the last captures I posted before.

GIF 25-Aug-20 11-14-07

You just can't know if these are two sprites or one. As it should.

I also tried some examples like the project here #35606 and that issue gets fixed too.

GIF 25-Aug-20 10-29-01

Camera smoothing is not a problem anymore and parallax planes work fine.

Pull request incoming :-)

All 11 comments

Related to https://github.com/godotengine/godot/issues/35606.

I guess we can alleviate this by making 2D gizmos snap to integer coordinates unless the zoom level is really high.

Further testing this, the amount of wobble seems to be one display pixel long regardless of the camera zoom level.

GIF 24-Aug-20 16-29-34

GIF 24-Aug-20 16-32-34

I really would like to help fix this but I'm at a loss right now at where should I take a peek. Could it be at the vertex shader side?

Peeking at vertex shader will not help - this is the basic fact that the smallest a video card can display is a single pixel, it cannot display sub-pixel.

The problem is not with display coordinates where of course you can't draw a pixel half way, but with world coordinates where you should be able to display your sprites at whatever world subpixel coordinates you want if the resolution and zoom allows for it. Just see the attached animated gifs to understand what I'm meaning, where sprites with same subpixel quantities all display accordingly. It's when the subpixel quantities vary that a sort of rounding happens and distorts the relative display positions.

If I erase the subpixel amounts of the coordinates everything displays as expected.

I think you answered your own issue. @Zireael07 is correct, aside from e.g. anti aliasing, you will tend to get sampling jitter in 2d unless you take steps to prevent it. This is a form of temporal aliasing.

This isn't a bug, more a feature, that you will find in all engines / APIs, unless you take steps to prevent it, such as snapping relative to the scene / background. See #35606 for more info.

If you don't understand why it happens, this is the equivalent to having a grid like e.g. a scrabble board, then get a photo and manually fill in dark areas in the grid to match the photo, and attempt to move the photo over the grid and repeat filling in dark areas. Notice that although the general appearance as shown in the filled in squares is the same, the exact pattern of filled in squares changes due to sampling.

If you want everything to look the same, you can take approaches such as:

  • move everything by 1 pixel (or multiples of this) increments each time, or
  • snap everything to the same scene coordinate system such that when a line traverses a pixel border, all the pixels will shift at the same time. (you will still get aliasing between lines if the coordinate system doesn't match the pixels).

If we go with our photo analogy, if the photo is pixellated with the grid scale that exactly matches the scrabble board, then even if the photo is at a float offset, you'll still see a pixel perfect match on screen and get a shift when a border is reached. If the pixellated photo scale is different, you'll get lines shifting across borders at different times. And if you have 2 photos that match the grid scale, but 1 is offset from the other by a float offset, you'll get jiggle as they cross boundaries at different times.

Allow me to politely disagree with you both :-) If I zoom the camera enough and place one sprite at X = 0 and other at X = 0.5 I would of course expect to see them at different positions on screen. If I put 10 sprites at X = 0.0, X = 0.1, X = 0.2, X = 0.3, X = 0.4, X = 0.5, X = 0.6, X = 0.7, X = 0.8, X = 0.9 I would expect them to be at ten different places if the camera has enough zoom, and, even more important, also maintain their relative positions when I move the camera. This bug is about erratic calculation of the sprite positions, not about if it is possible to move things with subpixel quantities, which of course it is. The very existence of the pixel snap option testifies this is obviously possible, and is your ticket out of it if you want everything to snap to world pixel coordinates. Godot IS drawing the sprites at different positions already. The problem is that depending on the relative camera position the sprites at non integer places jump at different display positions because there is a bug.

I can also confirm this is not limited to sprites but happens with ColorRects too, and everything inheriting from 2D canvas.

I even think some of the jitter people are reporting with 2D movement in Godot may in fact be caused by this issue. It certainly was part of it for me when we started our current project, as we were experiencing both jitter AND these undesirable sprite position jumps.

This is not a "feature", it is a bug, and a nasty one for what it matters. And the best way to prove my case is that I may have found the reason why it happens, and a way to fix the problem for good.

I want to test it a bit before proposing it for commit, and maybe discuss it a bit at Discord. Pull request incoming :-)

I think the fix is good. I'm trying different sceneries and it seems to work fine for me all of the time. Here is a small capture of the example project with a very zoomed camera showing two intersecting sprites with alpha borders and subpixel positions, getting perfect relative positioning in contrast to the last captures I posted before.

GIF 25-Aug-20 11-14-07

You just can't know if these are two sprites or one. As it should.

I also tried some examples like the project here #35606 and that issue gets fixed too.

GIF 25-Aug-20 10-29-01

Camera smoothing is not a problem anymore and parallax planes work fine.

Pull request incoming :-)

Just as a follow up, I have this fixed both on the master 4.0 and the 3.2 branches. I'll try to create the pull request in a few hours.

I just created two separate pull requests, one for the 4.0 branch and other for the 3.2 branch. Hope it's OK in this way.

I'm glad I was able to locate the source of the position distortion when moving the camera. Rock solid positioning of the sprites makes everything look so much better in my opinion :-)

Made the pull requests to my own fork instead of the base repository, fixed now.

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

Was this page helpful?
0 / 5 - 0 ratings