# How to avoid texture bleeding in a texture atlas?

In my game there is a Minecraft-like terrain made out of cubes. I generate a vertex buffer from the voxel data and use a texture atlas for looks of different blocks:

The problem is that the texture of distant cubes interpolates with adjacent tiles in the texture atlas. That results in lines of wrong colors between cubes (you may need to view the screenshot below at its full size to see the graphical flaws):

For now I use these interpolation settings but I tried every combination and even GL_NEAREST without mipmapping doesn't provide better results.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);


I also tried added an offset in the texture coordinates to pick a slightly smaller area of the tile but since the unwanted effect depends on the distance to the camera this cannot solve the problem completely. At far distances the stripes occur anyway.

How can I solve this texture bleeding? Since using a texture atlas is popular technique there might be a common approach. Sadly, for some reasons explained in the comments, I cannot change to different textures or texture arrays.

• The problem lies with mipmapping - your texture coordinates may work well for the biggest mip level, but as things get further away, the bordering tiles blend together. You can fight this by not using mipmaps (probably not a good idea), growing your guard zones (area between tiles), growing the guard zones dynamically in a shader based on mip level (tricky), do something strange while generating mipmaps (can't think of anything though).. I've never tackled this problem myself though, but there's stuff to start with.. =) – Jari Komppa Jan 7 '13 at 16:37
• As I said sadly turning off mipmapping doesn't solve the problem. Without mipmaps it will either interpolate linear GL_LINEAR which gives a similar result or pick the nearest pixel GL_NEAREST which anyhow also results in stripes between blocks. Last mentioned option decreases but not eliminates the pixel flaw. – danijar Jan 7 '13 at 16:44
• One solution is to use a format that have generated mipmaps, then you could create thees mipmaps your self and correct the error. Another solution is to force a specific mipmap level in your fragmentshader when you are sampling the texture. Another solution is to fill the mipmaps with the first mipmap. In other words, when you are creating your texture, you can just add subimages to that particular image, containing the same data. – Tordin Jan 7 '13 at 16:50
• I had this problem before and it was solved by adding 1-2 pixel padding in your atlas between each different texture. – Chuck D Jan 8 '13 at 6:01
• @Kylotan. The problem is about texture resizing regardless of if it is done by mipmaps, linear interpolation or nearest pixel picking. – danijar Jan 10 '13 at 20:34

Okay, I think you have two problems going on here.

The first problem is with mipmapping. In general, you don't want to naively mix atlasing with mipmapping, because unless all your subtextures are exactly 1x1 pixel sized, you will experience texture bleeding. Adding padding will simply move the problem to a lower mip level.

As a rule of thumb, for 2D, which is where you usually do atlasing, you don't mipmap; while for 3D, which is where you mipmap, you don't atlas (actually, you skin in such a way that bleeding won't be a problem)

However, what I think is going on with your program right now is that you're probably not using the correct texture coordinates, and because of that your textures are bleeding.

Let's suppose an 8x8 texture. You're probably getting the top-left quarter by using uv coordinates of (0,0) to (0.5, 0.5):

And the problem is that at u-coordinate 0.5, the sampler will interpolate the destination fragment using half of the left texel and half of the right texel. The same will happen at u-coordinate 0, and the same for v-coordinates. This is because you're addressing the borders between texels, and not the centers.

What you should do instead is address the center of each texel. In this example, these are the coordinates you want to use:

In this case, you will always be sampling the center of each texel and you should not get any bleeding... Unless you use mipmapping, in which case you will always get bleeding starting on the mip level where the scaled subtexture doesn't neatly fit inside the pixel grid.

To generalize, if you want to access a specific texel, the coordinates are calculated as:

function get_texel_coords(x, y, tex_width, tex_height)
u = (x + 0.5) / tex_width
v = (y + 0.5) / tex_height
return u, v
end


This is called "half pixel correction". There's a more detailed explanation in here if you're interested.

• Your explanation sounds very promising. Sadly since I have to video card at the moment, I cannot implement it yet. I will do so if the repaired card arrives. And I will calculate the mipmaps of the atlas on my own without interpolating over the boundaries of tiles then. – danijar Feb 18 '13 at 14:20
• @sharethis If you definitely have to atlas, make sure your subtextures have sizes in powers of 2, so you can limit bleeding to the lowest mip levels. If you do so, you don't really need to hand craft your own mipmaps. – Panda Pajama Feb 18 '13 at 14:30
• Even PoT sized textures can have bleed artifacts though, because the bilinear filtering can sample across those boundaries. (At least in the implementations I've seen.) As for the half-pixel correction, should that apply in this case? If I wanted to map his rock texture for example, surely that will always be 0.0 to 0.25 along the U and V coordinates? – Kylotan Feb 18 '13 at 17:31
• @Kylotan If your texture size is even, and all the subtextures have even sizes and are located at even coordinates, bilinear filtering when halving the texture size will not mix between subtextures. Generalize for powers of two (if you're seeing artifacts, you're probably using bicubic, not bilinear filtering). Regarding half-pixel correction, it is necessary, because 0.25 is not the rightmost texel, but the middle point between both subtextures. To put it into perspective, imagine that you are the rasterizer: what is the color of the texture at (0.00, 0.25)? – Panda Pajama Feb 19 '13 at 1:59
• @sharethis check out this other answer I wrote which may be able to help you gamedev.stackexchange.com/questions/50023/… – Panda Pajama Mar 9 '13 at 15:50

One option that'll be a lot easier than fiddling with mipmaps and adding texture coordinate fuzz factors is to use a texture array. Texture arrays are similar to 3d textures, but with no mipmapping in the 3rd dimension, so they're ideal for texture atlases where the "subtextures" are all the same size.

http://www.opengl.org/wiki/Array_Texture

• If they're available, they're a much better solution - you get other features as well, like wrapping textures, that you miss with atlases. – Jari Komppa Jan 7 '13 at 17:01
• @JariKomppa. I do not want to use texture arrays because that way I would have extra shaders for the terrain. Since the engine I write should be as general as possible I do not want to make this exception. Is there a way to create one shader that can handle both, texture arrays and single textures? – danijar Jan 10 '13 at 20:47
• @sharethis Dismissing unambiguously superiour technical solutions because you want to be "general" is a recipe for failure. If you write a game, don't write an engine. – sam hocevar Jan 11 '13 at 12:13
• @SamHocevar. I am writing an engine and my project is about a specific architecture were components are fully decoupled. So the form component shouldn't take care of the terrain component which should only provide standard buffers and a standard texture. – danijar Jan 11 '13 at 12:30
• @sharethis OK. I was somehow misled by the "In my game" part of the question. – sam hocevar Jan 11 '13 at 13:23

After struggling a lot with this issue, I finally came up with a solution.

To use both, a texture atlas and mipmapping, I need to perform the downsampling myself, because OpenGL would otherwise interpolate over the boundaries of tiles in the atlas. Moreover I needed to set the right texture parameters, because interpolating between mipmaps would also cause texture bleeding.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);


With these parameters, no bleeding occurs.

There is one thing you have to notice when providing custom mipmaps. Since it doesn't make sense to shrink the atlas even if each tile is already 1x1, the maximal mipmap level must be set according to what you provide.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 7); // pick mipmap level 7 or lower


Thanks for all other answers and very helpful comments! By the way I still do not know how to use linear up scaling, but I guess there is no way.

• good to know you got it solved. You should mark this answer as the correct one instead of mine. – Panda Pajama Mar 11 '13 at 3:40
• mipmapping is a technique exclusively for downsampling, which makes no sense for upsampling. The idea is that if you're going to draw a small triangle, it would take a lot of time to sample a large texture just to get a few pixels out of it, so you pre-downsample it and use the appropriate mip level when drawing small triangles. For larger triangles, using anything different from the larger mip level doesn't make sense, so it is an error to do something like glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_FILTER, GL_NEAREST_MIPMAP_LINEAR); – Panda Pajama Mar 11 '13 at 3:52
• @PandaPajama. You are right, I corrected my answer. To set GL_TEXTURE_MAX_FILTER to GL_NEAREST_MIPMAP_LINEAR causes bleeding not for the reason of mipmapping, but for the reason of interpolation. So in this case it is equivalent to GL_LINEAR since the lowest mipmap level is used anyway. – danijar Mar 11 '13 at 12:06
• @danijar - Can you explain in more detail how you perform the downsampling yourself, and how you tell OpenGL where (and when) to use the downsampled atlas? – Tim Kane Apr 27 '14 at 19:26
• @TimKane Split atlas into the tiles. For each mipmap level, downsample the tiles bilinearly, combine them, and upload that to the GPU specifying the mipmap level as parameter to glTexImage2D(). The GPU automatically chooses the correct level based on objects size on screen. – danijar Apr 27 '14 at 20:55

Looks like the issue could be caused by MSAA. See this answer and the linked article:

"when you turn on MSAA, it then becomes possible for the shader to get executed for samples that are inside the pixel area, but outside of the triangle area"

The solution is to use centroid sampling. If you are calculating the wrapping of the texture coordinates in a vertex shader, moving that logic to the fragment shader could also fix the issue.

• As I already wrote in another comment I have no working video card at the moment so I cannot check if that is the actual problem. I will do so as soon as I can. – danijar Feb 18 '13 at 14:21
• I disabled hardware antialiasing but the bleeding still remains. I already do the texture looking in the fragment shader. – danijar Mar 9 '13 at 11:41

This is an old topic, and I had a similar problem to the topic.

I had my texture coordinates just fine, but by lerping the camera position (which changed my element's positions) like so:

public static void tween(Camera cam, Direction dir, Vec2 buffer, Vec2 start, Vec2 end) {
if(step > steps) {
tweening = false;
return;
}

final float alpha = step/steps;

switch(dir) {
case UP:
case DOWN:
buffer = new Vec2(start.getX(), Util.lerp(start.getY(), (float)end.getY(), alpha));
cam.getPosition().set(buffer);
break;
case LEFT:
case RIGHT:
buffer = new Vec2(Util.lerp(start.getX(), end.getX(), alpha), start.getY());
cam.getPosition().set(buffer);
break;
}

step += 1;
}


The whole problem was Util.lerp() returns a float or double. This was bad because the camera was translating off-grid and causing some weird issues with texture snippets that where > 0 in X and > 0 in Y.

TLDR: don't tween or anything using floats