openglshader

Problems with uv when using half pixel translations


I am rendering 2 triangles to make a square, using a single draw call with GL_TRIANGLE_STRIP.

I calculate position and uv in the shader with:

vec2 uv = vec2(gl_VertexID >> 1, gl_VertexID & 1);
vec2 position = uv * 333.0f;
float offset = 150.0f;
mat4 model = mat4(1.0f);
model[3][1] = offset;
gl_Position = projection * model * position;

projection is a regular orthographic projection that matches screen size.

In the fragment shader I want to draw each first line of pixels with blue and each second line of pixels with red color.

int v = int(uv.y * 333.0f);
if (v % 2 == 0) {
    color = vec4(1.0f, 0.0f, 0.0f, 1.0f);
} else {
    color = vec4(0.0f, 0.0f, 1.0f, 1.0f);
}

This works ok, however if I use offset that will give me a subpixel translation:

offset = 150.5f;

The 2 triangles don't get matching uvs as seen in this picture:

2 neighboring triangles get different uv

What am I doing wrong?


Solution

  • Attribute interpolation is done only with a finite precision. This implies that due to round-off errors, even a difference in 1 ulp (unit-last-place, i.e. least significant digit) can cause the result to be rounded in the other direction. Since the order of operations in the hardware interpolation unit can be different between the two triangles, the values prior to rounding can be slightly different. OpenGL does not provide any guarantees about that. For example you might be getting 1.499999 in the upper triangle and 1.50000 in the lower triangle. Consequently when you add an offset of 0.5 then 1.99999 will be rounded down to 1.00000 but 2.000000 will be rounded to 2.00000.

    If pixel perfect results are important to you I suggest you calculate uv coordinates manually from the gl_FragCoord.xy. In a case of an axis-aligned rectangle, as in your example, it is straightforward to do.