Texture bugs

Hi guys.

I develop a 2D game using LWJGL3 and Box2D (via gdx-box2d). There is nothing special or complex in the game nor in it’s engine currently, but I’m facing with a problem.

The problem is, that as I start walking with my character, everything is fine, but when I reach the wall and then move, something is weird with the textures. Also, if I start the game with antialiasing, it’s more obvious that something is wrong.

Where and how should I start the bug finding? I’ve been programming for 17 years not, so I’m not a newbie, but relatively new in game development (1.5 years).

Some more details:

  • I use one texture as a texture atlas, and attach it to the shaders via sampler
  • I send the per tile texture offsets as an instanced variable

Check this video to see what I’m talking about: https://youtu.be/MZiS_Hr69wY

The code is available here if needed: https://github.com/mudlee/intermetto2D

This looks very much like texture bleeding due to 2x2/box-filtering over the border of the individual sprite in the texture atlas. When you use mipmapping and let OpenGL generate the mipmap levels, this even gets worse. Google/stackoverflow for solutions on how to avoid texture bleeding.

I tried several things today, no solution yet:

  • turn off multisampling
  • settings GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T and GL_TEXTURE_MIN/MAG_FILTER
  • not using transparent background for the image

Next, I try to add some padding between to the atlas tiles, but in the meanwhile, may I ask you to check the snippet that loads up my textures and the texture I use? You may find something evident I don’t see.

Code:

ByteBuffer image;
        int width;
        int height;

        try (MemoryStack stack = MemoryStack.stackPush()) {
            IntBuffer w = stack.mallocInt(1);
            IntBuffer h = stack.mallocInt(1);
            IntBuffer components = stack.mallocInt(1);

            image = stbi_load(Texture.class.getResource(file).getPath(), w, h, components, 4);
            if (image == null) {
                LOGGER.error("Failed to load texture {}, reason: {}", file, stbi_failure_reason());
                throw new RuntimeException("Failed to load texture " + file);
            }

            width = w.get();
            height = h.get();
        }

        int id = GL45.glCreateTextures(GL11.GL_TEXTURE_2D);
        GL45.glTextureStorage2D(id, 1, GL11.GL_RGBA8, width, height);
        GL45.glTextureSubImage2D(id, 0, 0, 0, width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, image);

        stbi_image_free(image);

The texture I use: https://raw.githubusercontent.com/mudlee/intermetto2D/master/src/main/resources/textures/main_sprite.png

UPDATE: I moved the texture offset calculation (see below) from the vertex shader to the fragment shader. Without antialiasing is almost fine (I still see some minor weird stuff).
I dont know if it helps others to help me out, but just wanted to share :slight_smile:

float x = (fs_in.vxTexture.x / fs_in.textureAtlasSettingsInstanced.z + fs_in.textureAtlasSettingsInstanced.x);
        float y = (fs_in.vxTexture.y / fs_in.textureAtlasSettingsInstanced.w + fs_in.textureAtlasSettingsInstanced.y);

        fragColor=texture(mainTextureSampler,vec2(x,y));

And this is how I calculate offsets:

int column = textureIndex % atlasGridDivision.x;
        int row = textureIndex / atlasGridDivision.x;
        float xOffset = (float) column / (float) atlasGridDivision.x;
        float yOffset = (float) row / (float) atlasGridDivision.y;
        this.offset.set(xOffset, yOffset);

UPDATE 2:

Found something that might solve the problem…
I used to use texture coords something like this:

1f, 1f,
0f, 1f,
1f, 0f,
0f, 0f

If I use this, the bleeding stuff is gone:

0.9f, 0.9f,
0.1f, 0.9f,
0.9f, 0.1f,
0.1f, 0.1f

Yes, this is what I meant by [iquote]texture bleeding due to 2x2/box-filtering over the border of the individual sprite[/iquote].

To explain a bit more, and it is important that you understand what is going on under the hood:

Typically, without multisampling, the graphics card will take a sample in the centroid of the framebuffer pixel. This in turn will yield an interpolated varying/in variable in your fragment shader (interpolated from the corresponding varying/out variable emitted from your vertex shader). You use this varying in your fragment shader to sample your texture atlas.

Now, that pixel centroid (and correspondingly the interpolated varying coordinate you use to sample your texture) can be just near the edge of your sprite texture in your texture atlas. When you use linear texture filtering this means that the graphics card will use a weighted 2x2 box filter (i.e. bilinear interpolation) of the nearest 4 texels that are around the sampled texture coordinate. The weights are computed based on how far away your sample point is from the individual four texels. In the worst case, your sample is just on the edge of your texture atlas sprite and two samples are taken from a part of your texture atlas you do not want to see.
To combat this, you need to either add a margin in your texture or (as you just did) scale down the texture coordinates towards the center of your sprite. Given a sprite texture size of W times H texels, the margin needs to be 0.5/W and 0.5/H. Why is this?
Because you want the worst case (pixel centroid on edge of sprite texture) to map to the exact texel center at the edge of the sprite texture. With an example sprite size of 5x5 texels, you want texture ordinate 0 to actually be 0.1, which maps to the center of the first texel.

I hope this helps a bit to understand the problem. :slight_smile: