Camera scrolling let textures flickering

Hey guys,

I’ve got some strange issues with my camera and the Aether.Physics2D.
I debugged it and got some results which explains why my textures are flickering.

now I want to follow my character with my camera. the character is a physics body which I push around.
after the physics step I take the Position and multiply it with 100 to get the new position for the camera.
But my camera got positions like this:

{X: 2,357056 Y: 2,356934}
{X: 2,357056 Y: 2,357056}
{X: 2,357178 Y: 2,357056}
{X: 2,357056 Y: 2,356934}
{X: 2,357178 Y: 2,357056}
{X: 2,357056 Y: 2,356934}
{X: 2,357056 Y: 2,357056}
{X: 2,357178 Y: 2,357056}
{X: 2,357056 Y: 2,356934}
{X: 2,357056 Y: 2,357056}

But it should something like

{X:2,121307 Y:2,121307}
{X:2,121307 Y:2,121307}
{X:2,121307 Y:2,121307}
{X:2,121307 Y:2,121307}
{X:2,121307 Y:2,121307}

because its every frame the same movement vector. That cause the very ugly flickering. How I can fix that?

EDIT: Ok I got it, It’s not just a physics problem. Can be reproduced with a diffrent texture too. I have attached a sample project to show my issue: https://www.dropbox.com/s/1q86gx5bkmslf2v/CameraFlickering.zip?dl=0

both textures can be testet. The gras-texture becomes darker if you scroll and the floor texture loose some lines of you scroll vertical.

Using Monogame 3.7.0.1708 under windows 10

You can try to set some friction or dumping to the object so it comes to rest (Sleep) after a while.
I assume this is a top-down view where the object doesn’t touch anything, in which case LinearDumping and AngularDumping can act as friction with the ground. However, dumping does not look very realistic. I f you run on the same problem where the object takes 4-5 sec to stop, you can apply a force on each frame to simulate friction on your own, or simply rely on dumping on the most part and when the velocity is very low , then apply a force to stop the object from moving.

Additionally, check your inputs to see that you are not sending very small forces on each frame that keeps the body awake (I have an Xbox controller for example that has a noisy analog pad).

Another way to attack this problem is to filter the positions you set on the camera.
You could either ignore small changes or filter out the noise. Here is a first-order low-pass filter.

const float g = 0; // range 0-1. A value of 1 completely cuts of the input.
const float a = (1f-g)/2f; 
Vector2 x1; // previous input  
Vector2 y1; // previous output
...
Vector2 x = body.Position; // input
Vector2 y = a * (x+x1) + g * y1;
x1 = x;
y1 = y;
Camera.position = y; // filtered output

You might notice that the camera no longer response immediately to changes but has a small delay.
Also I assume this runs on a constant time step.

Ok, I see. this is probably a rendering problem caused by very thin lines on the texture.
a couple of thinks to try,
-enable mipmaps in the content textures.
-use Linear or Anisotropic sampling when drawing.
-enable multisampling when you create the device.

Unless you all ready doing all this and this is something else.
A zoomed in screeshot or too with the differences would help.

Hey ppl, I updated my source to show the issue as simple as I can. If you use w,a,s,d to scroll you will see what I mean :slight_smile:

The texture

public class Camera2D
{
    public Vector2 Position { get; set; }
    public int ViewportWidth { get; set; }
    public int ViewportHeight { get; set; }

    public Vector2 ViewportCenter => new Vector2(this.ViewportWidth * 0.5f, this.ViewportHeight * 0.5f);

    public Matrix TranslationMatrix =>
        Matrix.CreateTranslation(-this.Position.X, -this.Position.Y, 0) *
        Matrix.CreateRotationZ(0) *
        Matrix.CreateScale(Vector3.One) *
        Matrix.CreateTranslation(new Vector3(this.ViewportCenter.X, this.ViewportCenter.Y, 0));   
}    

public class InputState
{
    public KeyboardState CurrentKeyboardState { get; private set; }
    public KeyboardState LastKeyboardState { get; private set; }

    public MouseState CurrentMouseState { get; private set; }
    public MouseState LastMouseState { get; private set; }

    public InputState()
    {
        this.CurrentMouseState = new MouseState();
        this.LastMouseState = new MouseState();
    }

    public void Update()
    {
        this.LastKeyboardState = this.CurrentKeyboardState;
        this.CurrentKeyboardState = Keyboard.GetState();
        this.LastMouseState = this.CurrentMouseState;
        this.CurrentMouseState = Mouse.GetState();
    }

    public bool IsKeyPressed(Keys key)
    {
        return this.CurrentKeyboardState.IsKeyDown(key);
    }

    public bool IsScrollLeft()
    {
        return this.IsKeyPressed(Keys.A);
    }

    public bool IsScrollRight()
    {
        return this.IsKeyPressed(Keys.D);
    }

    public bool IsScrollUp()
    {
        return this.IsKeyPressed(Keys.W);
    }

    public bool IsScrollDown()
    {
        return this.IsKeyPressed(Keys.S);
    }
}

public class Game1 : Game
{
    private readonly GraphicsDeviceManager graphics;
    private SpriteBatch spriteBatch;
    private Texture2D grasTexture;
    private readonly Camera2D camera = new Camera2D();
    private InputState inputState;

    public Game1()
    {
        this.graphics = new GraphicsDeviceManager(this);
        this.Content.RootDirectory = "Content";
        this.graphics.PreferredBackBufferWidth = 1280;
        this.graphics.PreferredBackBufferHeight = 720;
    }

    protected override void LoadContent()
    {
        this.spriteBatch = new SpriteBatch(this.GraphicsDevice);
        this.grasTexture = this.Content.Load<Texture2D>("floor");
        this.camera.ViewportWidth = this.graphics.GraphicsDevice.Viewport.Width;
        this.camera.ViewportHeight = this.graphics.GraphicsDevice.Viewport.Height;
        this.camera.Position = new Vector2(32 * 20, 32 * 20);
        this.inputState = new InputState();
    }

    private void HandleInput(GameTime gameTime, InputState inputState)
    {
        var cameraMovement = Vector2.Zero;

        if (inputState.IsScrollLeft())
        {
            cameraMovement.X = -1;
        }
        else if (inputState.IsScrollRight())
        {
            cameraMovement.X = 1;
        }

        if (inputState.IsScrollUp())
        {
            cameraMovement.Y = -1;
        }
        else if (inputState.IsScrollDown())
        {
            cameraMovement.Y = 1;
        }

        if (cameraMovement != Vector2.Zero)
        {
            cameraMovement.Normalize();

            var velocity = cameraMovement * 250 * (float)gameTime.ElapsedGameTime.TotalSeconds;
            this.camera.Position += velocity;
        }
    }

    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
            this.Exit();

        this.inputState.Update();

        this.HandleInput(gameTime, this.inputState);
        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        this.GraphicsDevice.Clear(Color.Black);

        this.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.LinearWrap);
        this.spriteBatch.Draw(this.grasTexture, Vector2.Zero, new Rectangle((int)-this.camera.TranslationMatrix.Translation.X, (int)-this.camera.TranslationMatrix.Translation.Y, this.GraphicsDevice.Viewport.Width, this.GraphicsDevice.Viewport.Height), Color.White);
        this.spriteBatch.End();

        base.Draw(gameTime);
    }
}