2

I'm using OpenGL through OpenTK in C# and try to load textures from generic bitmaps. My driver does not support NPOT Textures so what I do is allocate a POT Texture with GL.TexImage2D and fill it with my bitmap through GL.TexSubImage2D. However I have artifacts during drawing those textues. It seems, that it also draws an additional pixel at the bottom and right side of the texture. Shall I just subtract the pixel from the ratio or is there something else that is wrong?

Code for creation:

GL.BindTexture(TextureTarget.Texture2D, t.Name);

GL.PixelStore(PixelStoreParameter.PackAlignment, 1);
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, texture.W2, texture.H2, 0, PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero);
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, texture.DataSize.Width, texture.DataSize.Height, PixelFormat.Bgra, PixelType.UnsignedByte, data);

GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
GL.Ext.GenerateMipmap(GenerateMipmapTarget.Texture2D);

GL.BindTexture(TextureTarget.Texture2D, 0);

Code for drawing:

GL.BindTexture(TextureTarget.Texture2D, t.Name);

float x1 = 0;
float x2 = texture.WidthRatio;
float y1 = 0;
float y2 = texture.HeightRatio;

float rx1 = rect.X;
float rx2 = rect.X + rect.W;
float ry1 = rect.Y;
float ry2 = rect.Y + rect.H;

GL.Begin(BeginMode.Quads);

GL.TexCoord2(x1, y1);
GL.Vertex3(rx1, ry1, rect.Z + CGraphics.ZOffset);

GL.TexCoord2(x1, y2);
GL.Vertex3(rx1, ry2, rect.Z + CGraphics.ZOffset);

GL.TexCoord2(x2, y2);
GL.Vertex3(rx2, ry2, rect.Z + CGraphics.ZOffset);

GL.TexCoord2(x2, y1);
GL.Vertex3(rx2, ry1, rect.Z + CGraphics.ZOffset);

GL.End();

GL.Disable(EnableCap.Blend);
GL.BindTexture(TextureTarget.Texture2D, 0);

Where texture.WidhtRatio = DataSize.Width / W2;

Hooked
  • 84,485
  • 43
  • 192
  • 261
Flamefire
  • 5,313
  • 3
  • 35
  • 70

2 Answers2

1

You are using bilinear filtering, which will cause the last column and row to be interpolated with uninitialized texture data (hence the artifacts).

Assuming you wish to keep bilinear filtering enabled, what you can do is duplicate the last row and last column of your texture data texture. In other words, if your NPOT texture is 800x600, then upload 801x601 pixels with the last column/row duplicated.

This way, the interpolation on the last row/column will return the expected results and the artifacts should disappear.

Edit: as Reto Koradi suggested, this would only work when not using mipmaps. If you are using mipmaps, and especially if you are using anisotropic filtering, you should fill all of the empty region with the last column/row.

The Fiddler
  • 2,726
  • 22
  • 28
  • I thought about this when I originally saw the question. I don't think replicating one row/column is sufficient. This will work as long as only the lowest mipmap is used. But for higher mipmap levels, more of the uninitialized texture data will be used. I believe you have to fill the whole texture with replicated data to get clean sample values for all mipmaps. – Reto Koradi Sep 15 '14 at 16:49
  • You are right, this would be necessary if you are using `GL.GenerateMipmap()` and/or anisotropic filtering. I'll amend my answer. – The Fiddler Sep 16 '14 at 08:06
  • You are right. The problem arises with the mipmaps. However the other minFilters are really bad so do I really have to duplicate the last row/col all over the texture? Might be a bit slow (at least for cols)... – Flamefire Sep 27 '14 at 11:57
1

This is all to do with the edge cases of texturing. When you first look at textures in GL, most tutorials introduce the following:

  1. Filtering (what happens between texels)

    • Nearest
    • Linear

    The difference can be seen in the image below.

  2. Wrap Modes (what happens after the texture boundaries)

    Note the wrapping of colour even though texture coordinates are not outside -1 to 1 --- a combination of both linear filtering and repeat wrapping mode.

The other important thing that needs saying is samples are placed in the middle of pixels and texels. That's why gl_FragCoord.xy always has the fraction .5 (until you get to multi/super sampling etc). This can be seen in the image below. I suspect this is where the problem lies. You may want to adjust the texture coordinates to sample an inset of your texture to avoid interpolation of the neighboring pixels (i.e. x+0.5 to x+width-0.5).

enter image description here


Mipmaps will contain colour from a larger area, so simply changing the coordinates won't work. To re-create the behaviour of GL_CLAMP_TO_EDGE, you'd really need to extrude the sub image border manually. Alternatively, just clearing the texture before uploading would be much simpler. A fast way is to attach the texture to an FBO and glClear:

glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
glClear(GL_COLOR_BUFFER_BIT);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
//glDeleteFramebuffers(1, &fbo);

To go a step further and extrude the texture, I'd upload the sub image, then draw a quad over the sub image and desired border. In the fragment shader, discard for pixels inside the sub image and literally call coord = clamp(coord, imageMin, imageMax). To avoid double buffering, read while drawing. If you had multiple sub images, you could use the z-buffer here and draw square pyramids instead of quads, allowing the depth test to work like a minimum-distance-to-either-sub-image (just like drawing voronoi cells with cones)

Community
  • 1
  • 1
jozxyqk
  • 16,424
  • 12
  • 91
  • 180
  • +1 Really nice explanation. However the problem is with mip maps for downsized texture drawing. Any solutions here? – Flamefire Sep 27 '14 at 12:04
  • @Flamefire I guess it depends on the texture contents. Clearing it with alpha, or a background colour first might work (fast with FBO+glClear). If not, then as RetoKoradi suggests, adding a border might be better. If you only use the first few mipmap levels, the border could be small. You could also generate it in a fragment shader to speed things up. – jozxyqk Sep 28 '14 at 00:16
  • Could you provide a link how to use the FBO to achieve this? – Flamefire Sep 28 '14 at 11:03
  • Thanks! Clearing with transparent color works. (Had to add glSetColor(0) before and glSetColor(Color.Black) after the glClear call) – Flamefire Oct 04 '14 at 08:46