OpenGL: How to fix missing corner pixel in rect (lines or line loop)

Viewed 284

Take a look at the bottom-left corner of the green rectangles in the middle:

enter image description here

They're missing one pixel at the bottom left.

enter image description here

I drew those like this:

class Rect: public StaticModel {
  public:
    Rect() {  
        constexpr glm::vec2 vertices[] {
                {-0.5,0.5},  // top left
                {0.5,0.5},  // top right
                {0.5,-0.5},  // bottom right
                {-0.5,-0.5},  // bottom left
        };
        _buf.bufferData<glm::vec2>(vertices,BufferUsage::StaticDraw);
        _idxBuf.bufferData<GLuint>({0,1,3,2,0,3,1,2},BufferUsage::StaticDraw);
    }

    void bind() const override {
        _buf.bindVertex(); 
        _idxBuf.bind();
    }

    void draw() const override {
        gl::drawElements(8,DrawMode::Lines);
    }

  private:
    VertexBuffer _buf{sizeof(glm::vec2)};
    ElementArrayBuffer _idxBuf{};
};

That code is using a bunch of my helper methods/classes but you should be able to tell what it does. I tried drawing the rect using a simple GL_LINE_LOOP but that had the same problem, so now I'm trying GL_LINES and drawing all the lines in the same direction: top to bottom and left to right, but even still I'm missing a pixel.

These coordinates are going through orthographic projection:

gl_Position = projection * model * vec4(inPos, 0.0, 1.0);

So the shader is scaling those 0.5 coords up to pixel coords, but I don't think it's a rounding error.

Anything else I can try to get that corner to align?

1 Answers

OpenGL gives a lot of leeway for how implementations rasterize lines. It requires some desirable properties, but those do not prevent gaps when mixing x-major ('horizontal') and y-major ('vertical') lines.

  • First thing, the "spirit of the spec" is to rasterize half-open lines; i.e. include the first vertex and exclude the final one. For that reason you should ensure that each vertex appears exactly once as a source and once as destination:

      _idxBuf.bufferData<GLuint>({0,1,1,2,2,3,3,0},BufferUsage::StaticDraw);
    

    This is contrary to your attempt of drawing "top to bottom and left to right".

    GL_LINE_LOOP already does that though, and you say that it doesn't solve the problem. That is indeed not guaranteed to solve the problem because you mix x-major and y-major lines here, but you still should follow the rule in order for the next point to work.

  • Next, I bet, some of your vertices fall right between the pixels; i.e. the window coordinates fractional part is exactly zero. When rasterizing such primitives the differences between different implementations (or in our case between x-major and y-major lines on the same implementation) become prominent.

    To solve that you can snap your vertices to the pixel grid:

      // xy - vertex in window coordinates, i.e. same as gl_FragCoord
      xy = floor(xy) + 0.5
    

    You can do this either in C++, or in the vertex shader. In either case you'll need to apply the projection and viewport transformations, and then undo them so that OpenGL can re-apply them afterwards. It's ugly, I know.

The only bullet-proof way to rasterize pixel-perfect lines, however, is to render triangles to cover the shape (either each line individually or the entire rectangle) and compute the coverage analytically from gl_FragCoord.xy in the fragment shader.

Related