How to avoid visual glitches with smooth non-pixel-perfect 2d screen movement?

Hello,

first I want to thank the contributors to the project and everyone who is active in the forum. I‘m using Monogame as a hobby game-dev basically since XNA was discontinued and since then invested thousands of hours in game development with Monogame.

This is my first post and I also just saw the Patreon page: I immediately signed up for it in order to thank those who invest their time in this great project!

To my issue:
I‘m developing a 2d action game with fast player movement in 360 degrees and camera movement using a Vector2 screen position, following / centering the player with some smoothing of camera movement. The drawing is done via SpriteBatch.

Most of the static environment is grid based (64x64 px). The player and enemies are not locked to this grid and can move freely, having Vector2 positions.

This scenario comes with the issue of drawing stuff that is not snapped to device pixels, therefore creating blurriness.

I countered the issue by first drawing things to a separate rendertarget with screen position aligned to the 64px-grid. On the rendertarget the static grid-based objects are therefore drawn snapping perfectly to device pixels. This avoids lines/glitches between grid-based objects. Hope this illustration makes it clear what I do:

Now, when I finally draw the backbuffer with the full scene to the screen the image looks fine with any camera position when there is no camera-movement, also when the offset has decimals (e.g. -4.23,-1.85). The problem occurs when the camera is moving to follow the player: Static objects, especially those with sharp/detailed textures, appear slightly twitching/stutterish. This is due to the texture, not snapping to device pixels anymore, I guess. To be clear: The unwanted effect is not huge, but if there is way to make it better, I would like to know how.

I tried several things:

  • Snapping the screenposition to pixels from the beginning → solves the issue for texture-content, but makes the screen movement stuttering / no longer smooth => result is worse
  • changing sampler state when drawing backbuffer to screen to anything else but LinearClamp seems to make the problem worse or equal.

Does someone have experience with this issue? Any advise is highly appreciated. If this involves going to down to SharpDX or shaders, I’m fine with that. My target platform is Windows only.

Thanks!

1 Like

Pass rounded X/Y instead of float values when drawing. You can keep the vector (float) values so your calculations/speed/etc are smooth and accurate, but when using the value to draw your sprite just round it. Can be as easy as Vector2.Round(vector2var) within your spriteBatch.Draw command.

1 Like

To avoid the drawing to not snapping to the pixel and seeing lines between the grid you can pad each pixel on the edgie of your sprites around your spritesheet with its neighbouring pixel

You then wouldnt need to do all the other work on clamping your pixels

1 Like

Thanks for your responses. I tried rounding the positions given to spritebatch before. Problem is the screenmovement speed is dynamic. WIth the rounding approach this creates visible pixel-jumps depending on the speed of the screenmovement.

I’m very sure I need to draw the scene on not pixel perfect positions in order to have a smooth camera movement. Are there any things I can try to increase the quality when drawing with a moving screenposition with decimals? (or to simplify: How to increase quality of drawing without rounded positions).

Cheers.

Your render target is pretty low resolution, right? You could scroll the render target in back buffer resolution, which is probably higher.
Basically, move the camera by 1 back buffer pixel, rather than 1 render target pixel, if that makes sense.

2 Likes

For what it’s worth I call Math.Floor on Camera coordinates before firing off to Draw(). I also make the Camera chase its target by playing “catchup” for example,

Camera.X += (target.X - Camera.X) / 10;
Camera.Y += (target.Y - Camera.Y) / 10;

The result is satisfactory until I find something better.

1 Like

What is the quality issue you are having when not clamping your camera viewpoint?

Ive implemented smooth scrolling (when starting to scroll speed up until your at your scroll speed) at various speeds without any graphical issues.

Are you talking about texture bleeding or is it another issue?

1 Like

Sorry for late response, I was sick the last days. Thanks for your replies!

@markus
Interesting idea. I’ll try it out and tell you if it did something. The rendertarget is actually not really small (Full HD) but creating something double the size on windows should be no problem.

@Freddyf4
I’m doing pretty much the same. Any reason why you use floor instead of round?

@boot
Here are two screenshot. First is non-rounded screenposition and second is with rounded screenposition.

nonrounded

rounded

In the one with a rounded screenposition (therefore pixelperfect) the texture is much more crisp / clear. However the downside of it is, that the camera-movement is no longer as smooth. DId you somehow manage to get better results while doing smooth scrolling?

Ok guys, I found the solution for my problem. It was simpler than I thought, but you pointed me in the right direction. Thanks!

What did the trick finally: I’m now rounding the player position before giving it to the screen movement function, which basically does some rubber band effect. This way, the screen-position will have non-pixel-perfect positions while moving, but as soon as the screen/camera reaches it’s “target-position”, it is ensured that this position is pixel-perfect. Therefore a standing still screen will always draw pixel-perfect (good for image quality) and a moving screen will draw non-pixel-perfect (good for smooth camera movement).

So for everyone who stumbles on the same problem with 360 degree camera movement on a 2d scene, here is the summary of what I do:

Update:

  • ScreenTargetPosition = Player.Position.Round()
  • CurrentScreenPosition = WhateverFunctionYouLike(CurrentScreenPosition, ScreenTargetPosition), see @markus post for example

Draw:

  • Draw to a back buffer rendertarget as described in my initial post, using CurrentScreenPosition
  • Draw the back buffer rendertarget to screen, using the offset calculated in the previous step
1 Like