Archived

This topic is now archived and is closed to further replies.

T3hArtifex

Texture "Bleeding" - How can I make it stop?

Recommended Posts

In OpenGL (VB6), when rendering any texture, each pixel's color is blended with the pixels surrounding it. This is fairly unnoticable and quite nice when using high-resolution images, but when dealing with smaller images or portions of images, such as chipsets for tile-based terrain, one tile will have the colors of the surrounding tiles' edges "bleeding" on to it. Screenshot at: http://stocktracker2.tripod.com/bleeding.html I am currently using my own LoadTexture function: Public Sub LoadTexture(Texture As GLuint, sFile As String, Optional Transparent As Boolean, Optional Transparency As Single) Dim bmFile As BITMAPFILEHEADER Dim bmInfo As BITMAPINFOHEADER Dim bmRGB() As RGBQUAD Dim iFile As Integer Dim lImageSize As Long Dim iPixelSize As Integer Dim baImageData() As Byte Dim finalImageData() As Byte glGenTextures 1, Texture On Error GoTo ERR_H iFile = FreeFile Open sFile For Binary As iFile Get #iFile, , bmFile Get #iFile, , bmInfo If (bmInfo.biBitCount < 24) Then ReDim bmRGB(bmInfo.biClrUsed) Get #iFile, , bmRGB End If iPixelSize = bmInfo.biBitCount / 8 lImageSize = bmInfo.biWidth * bmInfo.biHeight * iPixelSize ReDim baImageData(lImageSize) ReDim finalImageData(lImageSize + bmInfo.biWidth * bmInfo.biHeight) Get #iFile, , baImageData Close #iFile If Not Transparent Then glBindTexture glTexture2D, Texture glTexParameteri glTexture2D, tpnTextureMagFilter, GL_LINEAR glTexParameteri glTexture2D, tpnTextureMinFilter, GL_LINEAR_MIPMAP_NEAREST gluBuild2DMipmaps glTexture2D, 3, bmInfo.biWidth, bmInfo.biHeight, tiBGRExt, _ GL_UNSIGNED_BYTE, ByVal VarPtr(baImageData(0)) Else For A = 0 To (lImageSize / 3) - 1 finalImageData(A * 4) = baImageData(A * 3) finalImageData(A * 4 + 1) = baImageData(A * 3 + 1) finalImageData(A * 4 + 2) = baImageData(A * 3 + 2) If baImageData(A * 3) = 255 And baImageData(A * 3 + 1) = 0 And baImageData(A * 3 + 2) = 0 Then finalImageData(A * 4) = 0 finalImageData(A * 4 + 3) = 0 Else finalImageData(A * 4 + 3) = 1 End If Next A glBindTexture glTexture2D, Texture glTexParameteri glTexture2D, tpnTextureMagFilter, GL_LINEAR glTexParameteri glTexture2D, tpnTextureMinFilter, GL_LINEAR_MIPMAP_NEAREST gluBuild2DMipmaps glTexture2D, 4, bmInfo.biWidth, bmInfo.biHeight, tiBGRAExt, _ GL_UNSIGNED_BYTE, ByVal VarPtr(finalImageData(0)) End If LoadGLTextures = True EXIT_H: Erase baImageData Erase finalImageData Exit Sub ERR_H: LoadGLTextures = False Resume EXIT_H End Sub As you can see, it uses, Linear/Linear_Mipmap_Nearest filtering, which I thought may have been the problem, but when I changed it to Linear/Linear filtering, the same problem still occured. Is there any way to stop the "bleeding"? If so, how? Thank you in advance for your assistance. [edited by - T3hArtifex on March 30, 2004 12:25:30 PM]

Share this post


Link to post
Share on other sites
I think Antialiasing is hardware related. What video card are you using? Check your driver''s settings.

Zorx (a Puzzle Bobble clone)
Discontinuity (an animation system for POV-Ray)

Share this post


Link to post
Share on other sites
The problem is with how the video card samples the texture. This is the same problem that Valve reported with Half-Life 2, a while back. The only way to get around it in OpenGL is to clamp texture coordinates with a fragment program, or to have a buffer-zone between each tile on the texture.

EDIT: Oops. . . not quite the same situation as Valve's, but similar.

[edited by - Ostsol on March 30, 2004 12:34:52 PM]

Share this post


Link to post
Share on other sites
Well, I tried using GL_POINT filtering, and I could see the difference, but it wasn''t quite what I wanted. This prevented textures from being blended nicely in the distance or at an angle, leaving a very pixelated feel to it, but in finer detail, the pixel colors still bleed. I may have to use the buffer method, although it seems a bit primitive...

Share this post


Link to post
Share on other sites
quote:
Original post by Ostsol
The only way to get around it in OpenGL is to clamp texture coordinates with a fragment program, or to have a buffer-zone between each tile on the texture.


Wrong.

First of all, this is not a problem, but a feature. It becomes a problem, if you don't know how to correctly use that feature.

T3hArtifex:

When using composite textures, you can run into two different problems with clamping: at the texture border, and between individual tiles. In the former case, you have to set the wrapping mode to GL_CLAMP_TO_EDGE, otherwise (when using GL_CLAMP), the hardware will interpolate to the border colour.

About the bleeding between two different tiles, the OpenGL specs precisely define the interpolation behaviour between two texels. For texturing, a constant value on the interpolation equation is defined by the texel center . Often people access texels by integer coordinates, and this is wrong (in this case).

So, if you access a tile in the compound texture like this (for example):

s, t from (10, 20) to (100, 150)

then you will struggle interpolation boundaries (at 1/2), and get bleeding at the edges. Instead, access it like this:

s, t from (10 + 0.5, 20 + 0.5) to (100 - 0.5, 150 - 0.5)

This compensates for the texel center, where the interpolation is set to a fixed value (ie. the interpolation factors are at 0, or 1 respectively). No bleeding will occur, and it works with linear, bilinear, and anisotropic filtering.

Note that the 0.5 bias needs only to be applied to the texture coordinates, not to the vertex XYZ coords ! Also note that the coordinates above assume that your texture coords are in the [0..max] range, and not [0..1]. This can be achieved either by using an appropriate texture matrix, or by using shorts instead of float for texcoords, or by dividing the texture coordinates presented above by the maximal texture resolution before feeding it to OGL.


[edited by - ALX on March 30, 2004 1:22:10 PM]

Share this post


Link to post
Share on other sites
Your solution will result in a small loss of image data and perhaps even some visual anomalies in tiling. Of course, it certainly is the most convenient solution. Apparently Valve didn''t think it was worth it, since they have mentioned that the two solutions they used require either centroid sampling (available on the Radeon 9500 and better ATI cards in D3D) or PS2.0 to get around the problem. Of course, their problem was not the same, as I mentioned. It only really appeared when anti-aliasing was enabled and the quad was rotated by a small amount. I posted a cheap little demo demonstrating this quite a while back on the OpenGL.org forums.

Share this post


Link to post
Share on other sites
quote:
Original post by Ostsol
Your solution will result in a small loss of image data and perhaps even some visual anomalies in tiling.


True, but this is generally no problem, provided the tile artist took this into consideration. It also it the easiest quick and dirty solution.

The best, widely accepted pixel perfect solution is definitely a 1 pixel wide buffer strip around the tile, with duplicated edge values. Although this requires a repositioning of all tiles, and might be a bit harder to integrate into an existing system.

Using a pixelshader to implement such a basic concept (that can be resolved using elementary techniques, not requiring any runtime processing) is a total waste of resources and pixelshader abuse IM(NSH)O. It will also greatly limit the useability of your code on lower end cards.

Share this post


Link to post
Share on other sites
Well, the border thing won''t work that well when the texture uses mipmapping or anisotropic filtering (at a distance or an angle). The border width requirement increases with the mipmap level, eg. if the app will ever use mipmap level 4, the border has to be at least 16 pixels wide on level 0. With a pixel shader that simulates CLAMP_TO_EDGE you can clamp it "perfectly" regardless of the mipmap level.

Share this post


Link to post
Share on other sites
Dont use gluBuild2DMipmaps, I have noticed that using it causes bleeding because it resizes the image to fit mipmaps.

I have had issues with it in the past where I tried to have sharpe masked edges on a sprite, my transparent color was purple and purple was some how being blended into the sprite around the transparent edges. after changeing to glTextImage2D(...); the problem was fixed.

"I seek knowledge and to help those who also seek it"

Share this post


Link to post
Share on other sites
But then you need to write your own mipmapping code, which (isn''t that hard but) seems a bit redundant... I guess it may be worth it for compatibility reasons though.

Share this post


Link to post
Share on other sites
This is a different issue to the one Valve had. Valve had a problem that when enabling multi sample AA, it was possible for the a triangle to affect a pixel even though the texture sample point was outside the triangle. This can cause problems since the texture at that coord might belong to a different triangle with a completely different colour. The result is aliasing. See this pdf for more details: http://download.nvidia.com/developer/presentations/GDC_2004/D3DTutorial_Sim.pdf

The OP is just having problems with bilinear filtering, which is completely different.

Share this post


Link to post
Share on other sites
As said above, it''s a pretty classic bilinear filtering problem. I''m pretty sure the guys at valve knew how bilinear filtering worked. Read up on it a bit, everything will become clear.

In short, the basic idea is that when you don''t have a 1:1 size for the picture it calculate the color value of a pixel using neighboring pixels to make an average. So when you tile textures in one file, the edge of one texture will be an avec of the pixels on your textures and of the edge of the texture right next to it.

Hope that helped, DR.

Share this post


Link to post
Share on other sites