Bleeding edges when artifically "zooming in" on texture atlas in the shaders.

I have a texture atlas with two blocks:
terrain

And I want to use this texture for my blocks. I don’t want each block to use the entire texture, just a part of it, to make the world look bigger, which is why I would’ve used something like this for the vertex shader:

v_uv = a_position;

Unfortunately since this isn’t a single texture, but an atlas, I had to improvise:

v_uv = vec2(0.5 * a_blocktype + mod(a_position.x, 0.5), -a_position.y * 0.5);

And this kinda works, except that it causes this bleeding:
Screenshot%20from%202019-01-01%2013-55-24

I’m not sure if this is due to no half-pixel correction, since it seems to be a bit more than a pixel of bleeding.

I suspect that this is a result of the implicit derivatives used for mipmap selection. When the texture coordinates wrap around, you have a large difference in texture coordinates between adjacent fragments, which will result in a low-resolution mipmap level being selected.

To correct this, use one of the texture functions which take explicit derivatives or an explicit LoD parameter, e.g. textureGrad or textureLod.

Also: when using a texture atlas, set glTexParameter(GL_TEXTURE_MAX_LOD) so you never use mipmap levels where the tiles are smaller than a texel. Or use a 2D array texture where each tile is a separate layer.

I’m not using mipmapping, though? This is 2D. Also GLSL 1.2 doesn’t have textureLod, should’ve mentioned the version.

Well, it’s a bit hard to see exactly what’s going on due to JPEG artifacts. But if this is with magnification, mipmaps won’t be the issue. It probably is “no half-pixel correction”, i.e. interpolating between the two tiles. If the sample location falls between the centre of a tile-edge pixel and the edge itself, linear interpolation will interpolate between the right-most pixel in one tile and the left-most pixel in the next tile.

With a texture atlas, you can’t use the GL_WRAP_* modes to handle tile boundaries. Solutions include:

[ul]
[li] Array textures (not available in OpenGL 2.1).
[/li][li] 3D textures (may be a suitable alternative to array textures if you don’t need mipmaps, although 2.1 doesn’t require support for more than 16 depth slices).
[/li][li] GL_NEAREST filtering.
[/li][li] Insetting texture coordinates by half a pixel (ugly; you’ll never get 1:1 reproduction at any zoom level).
[/li][li] Insetting texture coordinates by one pixel, adding a border to each tile.
[/li][li] Re-implementing linear filtering in the fragment shader so you can wrap around at tile boundaries (expensive).
[/li][li] Implementing clamp-to-edge in the fragment shader (less expensive than the previous option, but will result in smearing if tiles don’t have a plain border).
[/li][/ul]

If all your atlas tiles have the same size you could clamp the texture coordinates in the fragment shader to not go over the center of your border pixels.

I am using GL_NEAREST filtering already.
Here’s a better screenshot: