16

Edit: just so you know: I have not solved this problem perfectly yet, currently I am using 0.5px offset, it seems to work, but as others have said, it is not the "proper" solution. So I am looking for the real deal, the diamond exit rule solution didn't work at all.

I believe it is a bug in the graphics card perhaps, but if so, then any professional programmer should have their bullet-proof solutions for this, right?

Edit: I have now bought a new nvidia card (had ATI card before), and i still experience this problem. I also see the same bug in many, many games. So i guess it is impossible to fix in a clean way?

Here is image of the bug:

enter image description here

How do you overcome this problem? Preferrably a non-shader solution, if possible. I tried to set offset for the first line when i drew 4 individual lines myself instead of using wireframe mode, but that didnt work out very well: if the rectangle size changed, it sometimes looked perfect rectangle, but sometimes even worse than before my fix.

This is how i render the quad:

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glBegin(GL_QUADS);
    glVertex2f(...);
    glVertex2f(...);
    glVertex2f(...);
    glVertex2f(...);
glEnd();
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

Yes, I know I can use vertex arrays or VBO's, but that isn't the point here.

I also tried GL_LINE_LOOP, but it didn't fix the bug.

Edit: One solution is at, which works so far: Opengl pixel perfect 2D drawing by Lie Ryan:

Note that OpenGL coordinate space has no notion of integers, everything is a float and the "centre" of an OpenGL pixel is really at the 0.5,0.5 instead of its top-left corner. Therefore, if you want a 1px wide line from 0,0 to 10,10 inclusive, you really had to draw a line from 0.5,0.5 to 10.5,10.5.

This will be especially apparent if you turn on anti-aliasing, if you have anti-aliasing and you try to draw from 50,0 to 50,100 you may see a blurry 2px wide line because the line fell in-between two pixels.

Community
  • 1
  • 1
Rookie
  • 4,064
  • 6
  • 54
  • 86
  • How about two nested rectangles? – stark Apr 25 '12 at 17:09
  • @stark, how will that fix anything...? – Rookie Apr 25 '12 at 17:16
  • Make the white inner one 2*N narrower and shorter than the black outer one, where N is the width of your frame. This works if it is ok to fill the interior. If you really want a frame, then maybe try GL_LINE_LOOP. – stark Apr 25 '12 at 17:26
  • @stark, there is only a black rectangle in my pic. i thought it was obvious by making the outer area white too... Edit: GL_LINE_LOOP doesn't make a difference. – Rookie Apr 25 '12 at 17:48
  • possible duplicate of [Opengl pixel perfect 2D drawing](http://stackoverflow.com/questions/10040961/opengl-pixel-perfect-2d-drawing) – genpfault Apr 25 '12 at 17:58
  • How about to draw rectangles starting not from a corner but from a middle of any rectangle's side? – megabyte1024 Apr 25 '12 at 18:05
  • @genpfault: How is that even remotely what he asked about here? – Nicol Bolas Apr 25 '12 at 20:12
  • The way I'm reading it is that the OP is missing a pixel due to the diamond-exit rule, which the answer to that question covers. – genpfault Apr 25 '12 at 20:33
  • @genpfault, maybe you should post that as answer, since the current and only answer is wrong? I got it working by adjusting the coordinates by 0.5 pixels. – Rookie Apr 26 '12 at 16:52

4 Answers4

10

This is not a bug, this is exactly following the specification. The last pixel of a line is not drawn to prevent overdraw with following line segments, which would cause problems with blending. Solution: Send the last vertex twice.

Code Update

// don't use glPolygonMode, it doesn't
// do what you think it does
glBegin(GL_LINE_STRIP);
    glVertex2f(a);
    glVertex2f(b);
    glVertex2f(c);
    glVertex2f(d);
    glVertex2f(a);
    glVertex2f(a); // resend last vertex another time, to close the loop
glEnd();

BTW: You should learn how to use vertex arrays. Immediate mode (glBegin, glEnd, glVertex calls) have been removed from OpenGL-3.x core and onward.

datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • Could you give an example of that solution? For some reason, I am unable to fix it with that method. I tried with GL_LINES and GL_LINE_LOOP, neither worked. – Rookie Apr 25 '12 at 22:39
  • Looks like your answer is just plainly wrong, sending the last vertex has no effect. However, the second answer (and - for some reason - the not accepted one) here http://stackoverflow.com/questions/10040961/opengl-pixel-perfect-2d-drawing - has the correct fix! – Rookie Apr 26 '12 at 16:54
  • @Rookie: The second answer of the other question is not the accepted one because it is gibberish. It has nothing to do with the coordinates. It's all a matter of how the line is ended (diamond exit rule). If it doesn't work on your system you've got a buggy implementation. The OpenGL spec is the only thing that counts. – datenwolf Apr 26 '12 at 19:38
  • Okay, can you at least show the code how this can be fixed? Maybe I just messed it up. – Rookie Apr 26 '12 at 21:51
  • Shouldnt that code have GL_LINE_LOOP in it? AFAIK with GL_LINES you should send: a,b,b,c,c,d,d,a,a,a and not a,b,c,d,a,a. However, as i said before, i tried that and it didnt work... Just to be sure, i tried it again now, both ways (your code and my fixed code), but still it just doesnt work. I understand the diamond exit rule now and it makes sense to send the last vertex twice now. i think its a driver bug then... but i have newest drivers. – Rookie Apr 27 '12 at 23:23
  • @Rookie: Sorry I meant line strip. – datenwolf Apr 28 '12 at 07:54
  • @Rookie: May I ask what GPU you use, which OS and what driver? – datenwolf Apr 28 '12 at 10:36
  • ASUS EAH3450, Windows XP Pro 32bit SP3, driver 8.751.0.0 (newest) – Rookie Apr 28 '12 at 11:49
  • 1
    How does sending the last vertex change anything? You are still not exiting the diamond. – Troubadour Apr 30 '12 at 19:13
  • 1
    For the record, sending the last vertex twice didn't work for me either. – Syndog Feb 04 '15 at 23:19
10

Although you've discovered that shifting your points by 0.5 makes the problem go away it's not for the reason that you think.

The answer does indeed lie in the diamond exit rule which is also at the heart of the correctly accepted answer to Opengl pixel perfect 2D drawing.

The diagram below shows four fragments/pixels with a diamond inscribed within each. The four coloured spots represent possible starting points for your quad/line loop i.e. the window co-ordinates of the first vertex.

Possible vertex points outside a fragment's diamond

You didn't say which way you were drawing the quad but it doesn't matter. I'll assume, for argument's sake, that you are drawing it clockwise. The issue is whether the top left of the four fragments shown will be produced by rasterising either your first or last line (it cannot be both).

  1. If you start on the yellow vertex then the first line passes through the diamond and exits it as it passes horizontally to the right. The fragment will therefore be produced as a result of the first line's rasterisation.

  2. If you start on the green vertex then the first line exits the fragment without passing through the diamond and hence never exits the diamond. However the last line will pass through it vertically and exit it as it ascends back to the green vertex. The fragment will therefore be produced as a result of the last line's rasterisation.

  3. If you start on the blue vertex then the first line passes through the diamond and exits it as it passes horizontally to the right. The fragment will therefore be produced as a result of the first line's rasterisation.

  4. If you start on the red vertex then the first line exits the fragment without passing through the diamond and hence never exits the diamond. The last line will also not pass through the diamond and therefore not exit it as it ascends back to the red vertex. The fragment will therefore not be produced as a result of either line's rasterisation.

Note that any vertex that is inside the diamond will automatically cause the fragment to be produced as the first line must exit the diamond (provided your quad is actually big enough to leave the diamond of course).

Community
  • 1
  • 1
Troubadour
  • 13,334
  • 2
  • 38
  • 57
  • So the answer is "Diamond exit rule" ? Sorry for my stupidity, but how does that convert to an OpenGL code? – Rookie Apr 26 '12 at 21:56
  • 1
    @Rookie: No, if you read it carefully, I said the answer _lies_ in the diamond exit rule. I thought you might benefit from knowing the real reason shifting by 0.5 "solved" your problem. That way you wouldn't be surprised when another quad exhibits the exact same problem despite being shifted. Also, I find your comment odd given that you yourself requested genpfault to post an answer saying the pixel was missing due to the diamond exit rule. Anyway, with this information you can now try to solve your particular problem yourself with certainty about what is causing it. Don't mention it. – Troubadour Apr 26 '12 at 22:12
  • So nobody actually knows the solution? The only solution i can think of when i know that "diamond rule", is that i move the first line one pixel off to left, but as i said before, it doesnt work at all. I told genpfault to post the 0.5px shift as an answer, but i guess its not perfect answer either. so far it works... cant think of situation where it doesnt (unless i apply antialiasing). – Rookie Apr 26 '12 at 23:36
  • Maybe mention that the yellow vertex approach causes the pixel being drawn as a result of the rasterization of both the first *and* last line. – dialer Dec 27 '17 at 12:49
4

@Troubadour described the problem perfectly. It's not a driver bug. GL is acting exactly as specified. It's designed for sub-pixel accurate representation of the world space object in device space. That's what it's doing. Solutions are 1) anti-alias, so the device space affords more fidelity and 2) arrange for a world coordinate system where all transformed vertices fall in the middle of a device pixel. This is the "general" solution you are looking for.

In all cases you can achieve 2) by moving the input points around. Just shift each point enough to take its transform to the middle of a device pixel.

For some (unaltered) point sets, you can do it by slighly modifying the view transformation. The 1/2 pixel shift is an example. It works e.g. if the world space is an integer-scaled transform of device space followed by a translation by integers, where world coordinates are also integers. Under many other conditions, though, +1/2 won't work.

** Edit **

NB as I said a uniform shift (1/2 or any other) can be built into the view transform. There is no reason to fiddle with vertex coordinates. Just prepend a translation, e.g. glTranslatef(0.5f, 0.5f, 0.0f);

Gene
  • 46,253
  • 4
  • 58
  • 96
  • "arrange for a world coordinate system where all transformed vertices fall in the middle of a device pixel." doesnt that mean exactly the solution im using currently: move 0.5 pixels in x/y axes? isnt there any other solutions? how do you build it into the view transform? – Rookie Aug 28 '12 at 12:16
  • I tried to express that the 0.5 pixels make sense _if_ you have integer coordinates equivalent to device space (and a number of other special cases). Nearly all OpenGL apps have entirely different world and device coordinate spaces. Much of OpenGL is about accomodating this difference. I'll add a comment about how to do a shift in the transform. – Gene Aug 28 '12 at 18:56
-1

Try change 0.5 into the odd magic number that's used everywhere 0.375. Used be opengl, and X11 etc.

Becuase of that diamond rule mentioned and how graphiccards draw to avoid unnecessery overdraws of pixels.

Provide some link but there's lots of them, just search keywords opengl 0.375 diamond rule if you need more information. It's about how outlines and fills are treated algorithmically in opengl. It's needed for pixelperfect rendering of textures in for example 2d sprites aswell.

Take a look at this.

Want to add something; So doing what you want, implementing diamond rule implemented in code would be simply one liner; change 0.5 into 0.375 like this; And it should render properly.

glTranslatef(0.375, 0.375, 0.0);
Carsten
  • 11,287
  • 7
  • 39
  • 62