Sprite Distortion (Ghosting While Moving and Artifacts when Mirroring to External Display)

I am working on my first MonoGame project. I love the framework so far!

I have implemented my own letterbox/pillarboxing to scale my native resolution by the maximum integer scale allowable on my display. Basically, I determine the maximum integer scale, set my PreferredBackBuffer to the screen resolution, create a Viewport that is my native resolution * maximum scale, and then set my SpriteBatch to draw everything at Matrix.CreateScale(max_scale).

This works much better than rendering to a texture that has my native resolution and then scaling it up. (Scaling using a Matrix in SpriteBatch, as opposed to just rendering to a texture and then scaling it up, allows you to fake “subpixel rendering” to some extent).

That said, I am facing two issues.

  1. When my sprite moves, there is very subtle “ghosting” happening. The sprite is subtly blurry and there’s a faint ghostly trail behind it.

  2. When I mirror to an external monitor, there is less ghosting, but there is ugly artifacting on the outside of sprite when stationary. See below.

enter image description here

Rounding my player’s position to integers in the Draw() command doesn’t help for either problem.

Does anyone have any thoughts about how to fix these issues?

Here is my game class:

using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input;  namespace MyMonoGame {     public class MyGame : Game     {         // declare variables         GraphicsDeviceManager graphics;         SpriteBatch sprite_batch;          // resolution management         int native_width;         int native_height;         int screen_width;         int screen_height;         int max_scale;         int horizontal_margin;         int vertical_margin;          // objects         Player player;          public MyGame()         {             // create GraphicsDeviceManager instance             graphics = new GraphicsDeviceManager(this);             // specify root directory             Content.RootDirectory = "Content";         }          protected override void Initialize()         {             // set window title             this.Window.Title = "My Game";              // create SpriteBatch instance, which can be used to draw textures.             sprite_batch = new SpriteBatch(GraphicsDevice);              // initialize some variables             native_width = 160;             native_height = 144;              // resolution management             // get screen size             screen_width = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;             screen_height = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;             // get max_scale, the maximum integer scale that will fit on the screen             // note: must be integer to prevent pixel distortion             int width_divisor = (int) Math.Floor((float)screen_width/(float)native_width);             int height_divisor = (int) Math.Floor((float)screen_height/(float)native_height);             max_scale = Math.Min(width_divisor, height_divisor);             // get margins for letterboxing and pillarboxing             int max_width = native_width * max_scale;             int max_height = native_height * max_scale;             horizontal_margin = (int)((screen_width - max_width)/2f);             vertical_margin = (int)((screen_height - max_height)/2f);              // toggle fullscreen             graphics.PreferredBackBufferWidth = screen_width;             graphics.PreferredBackBufferHeight = screen_height;             graphics.ToggleFullScreen();             GraphicsDevice.Viewport = new Viewport(horizontal_margin, vertical_margin, native_width * max_scale, native_height * max_scale);             graphics.ApplyChanges();              // objects             player = new Player(this);         }          protected override void LoadContent()         {         }          protected override void UnloadContent()         {         }          protected override void Update(GameTime gameTime)         {             if (Keyboard.GetState().IsKeyDown(Keys.Escape))                 Exit();              // update objects             player.Update(gameTime);         }          protected override void Draw(GameTime gameTime)         {             // clear window & fill with solid color             GraphicsDevice.Clear(Color.DarkRed);              // draw objects             var transform_matrix = Matrix.CreateScale(max_scale);             sprite_batch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.None, RasterizerState.CullCounterClockwise, transformMatrix: transform_matrix);             player.Draw(sprite_batch);             sprite_batch.End();         }     } } 

Unity Ghosting Effect While Playeing my Game

There is ghosting or motion blur effect on my player while the game is running and I can’t identify why. if I take a screenshot of it there is no ghosting on the image but it is visible while playing the game. I can run my game at 144FPS on a 144hz display but the ghosting still occurs


I was able to take this picture using my phone camera. if I use ShareX on my computer there is no motion bluer to be seen

playermovemt script

using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMovement : MonoBehaviour {      public string playerInput;      [SerializeField]private string H;     [SerializeField]private string V;     [SerializeField]private string F;     void Start()     {         H = "Horizontal" + playerInput;         V = "Vertical"+ playerInput;         F = "Fire1"+ playerInput;     }      [Range(0, 16)] public float speed;      private Vector3 movement;     private Vector3 tryMovement;     public LayerMask whatIsWall;     public float movementCheckDistance = 1;     public Transform transformModel;     void moveIfPossible()     {         if (Physics.Raycast(transform.position, tryMovement, movementCheckDistance, whatIsWall))         {             Debug.DrawRay(transform.position, tryMovement * movementCheckDistance, Color.blue);             while (Input.GetAxisRaw(H) == 0 || Input.GetAxisRaw(V) == 0)             {                 return;             }         }         else         {             movement = tryMovement;             //SetModelRotation(movement);             SetModelRotation();         }     }     /* void SetModelRotation(Vector3 movement)     {         if (movement == Vector3.back)             transformModel.rotation = Quaternion.Euler(0, 180, 0);         if (movement == Vector3.forward)             transformModel.rotation = Quaternion.Euler(0, 0, 0);         if (movement == Vector3.left)             transformModel.rotation = Quaternion.Euler(0, 270, 0);         if (movement == Vector3.right)             transformModel.rotation = Quaternion.Euler(0, 90, 0);     } */       Dictionary<Vector3, int> rotationsEulerY = new Dictionary<Vector3, int>     {         {Vector3.left, 270}, {Vector3.right, 90}, {Vector3.back, 180}, {Vector3.forward, 0},{Vector3.zero,0}     };     void SetModelRotation()     {         //Debug.Log(movement);         transformModel.rotation = Quaternion.Euler(0, rotationsEulerY[movement], 0);     }     void CheckForInput()     {         if (Input.GetAxisRaw(H) == 1 || Input.GetAxisRaw(H) == -1)         {             tryMovement = new Vector3(Input.GetAxisRaw(H), 0, 0);             moveIfPossible();         }         else if (Input.GetAxisRaw(V) == 1 || Input.GetAxisRaw(V) == -1)         {             tryMovement = new Vector3(0, 0, Input.GetAxisRaw(V));             moveIfPossible();         }         return;     }     void FixedUpdate()     {         moveIfPossible();         CheckForInput();         GetComponent<Rigidbody>().velocity = movement * speed;         animator.SetFloat("runningSpeed", GetComponent<Rigidbody>().velocity.magnitude);     }     void OnCollisionEnter(Collision collision)     {         if (collision.gameObject.tag == "Wall")         {             CorrectPosition();         }     }     void OnCollisionExit(Collision collision)     {         CorrectPosition();     }     void CorrectPosition()     {         Vector3 posNow = transform.position;         posNow.x = Mathf.RoundToInt(gameObject.transform.position.x);         posNow.y = Mathf.RoundToInt(gameObject.transform.position.y);         posNow.z = Mathf.RoundToInt(gameObject.transform.position.z);         transform.position = posNow;         movement = Vector3.zero;     }      public GameObject shot;     public float fireRate;     public float shotSpeed;     private float nextFire;     GameObject cloneBullet;     public float shakeDuration;     public AudioSource ShotGunSound;     void ShootIfPossible()     {         if (Input.GetButton(F) && Time.time > nextFire)         {             nextFire = Time.time + fireRate;             cloneBullet = (GameObject)Instantiate(shot, transform.position + tryMovement, Quaternion.identity);             cloneBullet.GetComponent<Rigidbody>().velocity = shotSpeed * tryMovement;             ShotGunSound.Play();         }     }     public Animator animator;     void Update()     {         ShootIfPossible();     }  }