Search code examples
c#floating-pointxnatexturesmonogame

Strange texture shifting bug when using PointClamp or PointWrap


I am making a small RPG game and I'm currently on window rendering (things like inventory/quest windows).

I draw the actual window with 9 quads textured from a "skin", 4 corners, 4 borders and the middle.

The problem is, at arbitrary coordinates a triangle or two shifts slightly downwards. It happens consistently, and only if PointClamp or PointWrap is used. Other opions work fine but they give a blurry look.

The same bug actually happens with the green bar above, however I "fixed" it by using Linear instead of Point.

I use this function to convert from pixel coordinates to screen coordinates:

Note: This function works as I originally expected in MonoGame.

    public static Vector3 PixelToScreen(GraphicsDevice device, float X, float Y)
    {
        float xscale = (float)device.Viewport.Width / 2;
        float yscale = (float)device.Viewport.Height / 2;
        return new Vector3(((int)X / xscale) - 1f, 1f - ((int)Y / yscale), 0);
    }

I suspect this function might be the source of my problem. Is there a "right way" to do it?

A picture is worth a thousand words, so here's a screenshot with the problem.

https://i.sstatic.net/rNsnH.png

I am quite sure the solution is trivial, but I just can't catch it.

UPDATE

After some more digging and researching, I finally found a solution. It turns out that it has something to do with how texture pixels are mapped to the screen pixels.

UPDATE #2

After porting this code to MonoGame, I've noticed a different bug where everything looks "blurry". Curiously enough, removing the offset (reverting to the original function) fixes the problem!

The Fix

 public static Vector3 PixelToScreen(GraphicsDevice device, float X, float Y)
    {
        X -= 0.5f; // Offset the "pixel value" by half a pixel
        Y -= 0.5f; // To provide "expected results" use negative value
        float xscale = (float)device.Viewport.Width / 2;
        float yscale = (float)device.Viewport.Height / 2;
        return new Vector3((X / xscale) - 1f, 1f - (Y / yscale), 0);
    }

More on the topic:

Directly Mapping Texels to Pixels (Direct3D 9)

Understanding Half-Pixel and Half-Texel Offsets

This question is obviously resolved now, but I hope my findings will help someone stuck in the same situation.


Solution

  • The Fix

     public static Vector3 PixelToScreen(GraphicsDevice device, float X, float Y)
        {
            X -= 0.5f; // Offset the "pixel value" by half a pixel
            Y -= 0.5f; // To provide "expected results" use negative value
            float xscale = (float)device.Viewport.Width / 2;
            float yscale = (float)device.Viewport.Height / 2;
            return new Vector3((X / xscale) - 1f, 1f - (Y / yscale), 0);
        }
    

    Do not use these offsets in MonoGame as it somehow takes care of the matters internally.