When you draw a sprite with rotation its 4 vertices are rotated. Depending on position and rotation these vertices may have some non-integer values. During the rasterization, it cannot color only a part of a pixel (unless using anti-aliasing, I am using point sampling), either a pixel is filled or not. So depending on the rounding the rotated sprite is not just displaced, different pixels are filled. Is there a way to ensure consistent rasterization?
For example I have this arrow sprite
When I draw it at (x.0f, x.0f) rotated 1 degree (similar problem for other rotations, e.g. 45 degrees) it appears like so (upscaled image):
But when I draw it at (x.1f, x.1f) rotated 1 degree it appears like so (upscaled image):
Code to reproduce (cross-platform OpenGL MonoGame 3.8.0):
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace RotationTest
{
public class Game1 : Game
{
private GraphicsDeviceManager graphics;
private SpriteBatch spriteBatch;
private Texture2D texture;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
texture = Content.Load<Texture2D>("Arrow");
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Transparent);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.Default, null, null, null);
spriteBatch.Draw(texture, new Vector2(100, 100), texture.Bounds, Color.White, MathHelper.ToRadians(45f),
Vector2.Zero, Vector2.One, SpriteEffects.None, 0f);
spriteBatch.End();
base.Draw(gameTime);
}
}
}
Usually, in a game, there is also a WVP matrix, so simply rounding the position will not be enough. When the camera moves the inconsistent rasterization will look weird as the shape of the sprite appears to flicker. I could in a custom vertex shader apply world+view matrix, then truncate the vertices, and then apply the projection matrix. This does not seem to fix the issue for me and introduces jittering on camera movement. I could try to reproduce it in a much simpler environment like this. But first, is there really not a better solution?