Flamefire
Flamefire

Reputation: 5807

Using OpenGL GL.TexSubImage2D for NPOT Textures results in artifacts

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;

Upvotes: 2

Views: 868

Answers (2)

jozxyqk
jozxyqk

Reputation: 17256

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)

Upvotes: 1

The Fiddler
The Fiddler

Reputation: 2794

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.

Upvotes: 1

Related Questions