Search code examples
opengltexturesrenderingartifacts

Artifacts with rendering texture with horizontal and vertical lines with OpenGL


I created 8x8 pixel bitmap letters to render them with OpenGL, but sometimes, depending on scaling I get weird artifacts as shown below in the image. Texture filtering is set to nearest pixel. It looks like rounding issue, but how could there be some if the line is perfectly horizontal.

Left original 8x8, middle scaled to 18x18, right scaled to 54x54.

Left original 8x8, middle scaled to 18x18, right scaled to 54x54.

Vertex data are unsigned bytes in format (x-offset, y-offset, letter). Here is full code:

vertex shader:

#version 330 core

layout(location = 0) in uvec3 Data;

uniform float ratio;
uniform float font_size;

out float letter;

void main()
{
  letter = Data.z;

  vec2 position = vec2(float(Data.x) / ratio, Data.y) * font_size - 1.0f;
  position.y = -position.y;

  gl_Position = vec4(position, 0.0f, 1.0f);
}

geometry shader:

#version 330 core

layout (points) in;
layout (triangle_strip, max_vertices = 4) out;

uniform float ratio;
uniform float font_size;

out vec3 texture_coord;

in float letter[];

void main()
{
// TODO: pre-calculate
  float width = font_size / ratio;
  float height = -font_size;

  texture_coord = vec3(0.0f, 0.0f, letter[0]);
  gl_Position = gl_in[0].gl_Position + vec4(0.0f, height, 0.0f, 0.0f);
  EmitVertex();

  texture_coord = vec3(1.0f, 0.0f, letter[0]);
  gl_Position = gl_in[0].gl_Position + vec4(width, height, 0.0f, 0.0f);
  EmitVertex();

  texture_coord = vec3(0.0f, 1.0f, letter[0]);
  gl_Position = gl_in[0].gl_Position + vec4(0.0f, 0.0f, 0.0f, 0.0f);
  EmitVertex();

  texture_coord = vec3(1.0f, 1.0f, letter[0]);
  gl_Position = gl_in[0].gl_Position + vec4(width, 0.0f, 0.0f, 0.0f);
  EmitVertex();

  EndPrimitive();
}

fragment shader:

#version 330 core

in vec3 texture_coord;

uniform sampler2DArray font_texture_array;

out vec4 output_color;

void main()
{
  output_color = texture(font_texture_array, texture_coord);
}

Solution

  • Well, when using nearest filtering, you will see such issues if your sample location is very close to the boundary between two texels. And since the tex coords are to be interpolated separately for each fragment you are drawing, slight numerical inaccuracies will result in jumping between those two texels.

    When you draw an 8x8 texture to an 18x18 pixel big rectangle, and your rectangle is perfectly aligned to the putput pixel raster, you are almost guaranteed to trigger that behavior:

    Looking at the texel coodinates will then reveal that for the very bottom output pixel, the texture coords would be interpolated to 1/(2*18) = 1/36. Going one pixel up will add 1/18 = 2/36 to the t coordinate. So for the fifth row from the bottom, it would be 9/36.

    So for the 8x8 texel big texture you are sampling from, you are actually sampling at unnormalized texel coordinates (9/36)*8 == 2.0. This is exactly the boundary between the second and third row of your texture. Since the texture coordinates for each fragment are interpolated by a barycentric interpolation between the tex coords assigned to the three vertices froming the triangle, there can be slight inaccuracies. And even the slightest possible inaccuracy representable in floating point format would result in flipping between two texels in this case.

    I think your approach is just not good. Scaling bitmap fonts is always problematic (maybe besides integral scale factors). If you want nicely looking scalable texture fonts, I recommend you to look into signed distance fields. It is quite a simple and powerful technique, and there are tools available to generate the necessary distance field textures.

    If you are looking for a quick hack, you coud also just offset your output rectangle slightly. You basically must make sure to keep the offset in [-0.5,0.5] pixels (so that never different fragments are generated during rasterization, and you must make sure that all the potential sample locations will never lie close to an integer, so the offset will depend on the actual scale factor.