1
$\begingroup$

I have written a software rasterizer, and have encountered a (literal) edge case.

At the silhouette edge of a typical object, you have two triangles—the front face and the back face. They are not coplanar, but they of course share that single edge. Say the pixel center / subpixel sample happens to lie very close to this edge—so close that the depth value computed for both triangles is actually exactly, bit-for-bit, identical.

Without special handling for this case, sometimes the "wrong" fragment will get through. For example, with the < depth function, if the backface renders first, then the depth test for the frontface will fail and the backface will show through. If the frontface renders first, then the depth test for the backface will fail and the frontface will show. The former case produces the wrong image—sometimes quite objectionably so.

I have thought about hacks to fix this like trying to handle the "equals" case explicitly, depending upon the depth function and whether the fragment came from a front face, but it's not clear, especially for the and depth functions, what the "right" answer is. Still, it's about the only thing I can think of trying.

This is basically Z-fighting, but unlike the usual Z-fighting graphics newbies encounter when they don't set the clipping planes right or whatever (and which makes this issue almost ungoogleable), existing hardware definitely does provide some kind of solution to handle this particular case, because this problem does not occur on real GPUs.

So my question is—how is this handled by real GPUs? If that's unanswerable, then how can it be handled consistently?

$\endgroup$
4
  • $\begingroup$ Doesn't this just fall back to triangle submission order, i.e. whichever triangle was later in the index buffer wins, for depth functions that include "equals", and whichever was earlier wins, for depth functions that don't include "equals"? GPUs are required to respect submission order in terms of the final contents of the framebuffer, even if they process things in parallel and out-of-order along the way through the pipeline. It's a similar case to rendering transparent objects back-to-front, with depth writes disabled. $\endgroup$ Commented Mar 11, 2021 at 18:13
  • $\begingroup$ @NathanReed I thought of that; I think there must be some sort of exception for submission order here. Consider <. If fragments from later indices lose, then we could get the wrong fragment on edges just by having the front triangle happen to render after the back triangle. This would expose the problem in most engines just by rotating the model around. $\endgroup$
    – geometrian
    Commented Mar 13, 2021 at 1:46
  • $\begingroup$ I guess there could be some extra rule like that, but I've never heard of it; nor can I find anything like that in either the Direct3D or OpenGL rasterizer and depth-test specs. There's no hint in there of any interaction between face orientation and either rasterization rules or depth testing. Honestly I think in practice we almost always have backface culling enabled and so in most use cases / engines you would not see this problem. I would test it directly but my home codebase isn't well set-up to do that right now. $\endgroup$ Commented Mar 13, 2021 at 19:07
  • $\begingroup$ This would appear to be a related question: computergraphics.stackexchange.com/q/3619/209 $\endgroup$
    – Simon F
    Commented Mar 16, 2021 at 14:05

2 Answers 2

1
$\begingroup$

If you are taking about this occurring within a single draw call for a single object then you are talking about rasterization not Z fighting. So the real question is, "how are two triangles rasterized that are right next to each other". And the answer to that question boils down to rasterization rules. Which must be enforced, on GPU's that happens in hardware, for your software rasterizer you need to pick a rule set, then enforce it in code. Generically a rasterizer can leave no holes, and each pixel is only drawn once. You may want to always add colors when drawing your triangles, start with a color of zero and just add new colors drawn to that pixel, this will make your overdraw stand out like a sore thumb.

The most widely know rule set for handling this is the "top left rule" which states: The center of every pixel must lie completely inside the triangle, (you are computing pixel centers right?) or it center lies exactly on a triangle edge or on multiple edges in the case of corners, and a left edge is a non-horizontal edge that is on the left side of the triangle.

This last bit is directly from Wikipedia.

$\endgroup$
4
  • $\begingroup$ This is correct as far as it goes, but does not address the problem. Raster rules (and so, sample coverage) is already correctly implemented. $\endgroup$
    – geometrian
    Commented Mar 11, 2021 at 15:20
  • $\begingroup$ Rasterization rules do in fact cover this situation, I suggest you look at it more closely, Z fighting doesn't occur on triangles that share an edge because they the edges do not overlap when implemented correctly. $\endgroup$
    – pmw1234
    Commented Mar 11, 2021 at 15:25
  • 1
    $\begingroup$ @pmw1234: Think of the letter V. Imagine that represents looking down at two triangles sharing an edge at the point of the "V". If you look from the right, one face faces towards you and the other away. Both triangles at the tip of the "V" rasterize the same samples (relative to the camera). That is, both triangles own the same "top-left" parts of each pixel, so they rasterize on top of each other. The samples from the tip can be arbitrarily close to each other in depth space. $\endgroup$ Commented Mar 11, 2021 at 15:40
  • $\begingroup$ I think this is the wording of this question, which is starting to become circular. There are the edges of triangles and the process drawing that edge such that no fragments are drawn twice, and two triangles that are essentially coplanar whose fragments effectively overlap. Your response answers the second case, my response answers the edge case, in either case the original question has been answered. $\endgroup$
    – pmw1234
    Commented Mar 11, 2021 at 15:56
0
$\begingroup$

The way this is "handled by real GPUs" is that most models are closed. As such, surfaces facing away from the camera are definitely not closer than some other surface of that object. To prevent throwing away performance rasterizing triangles that definitely do not contribute to the final image, most users turn on backface culling.

And thus, there is no problem.

In the absence of backface culling, what you describe is effectively just a form of z-fighting. But note that z-fighting almost always happens very far away from the camera and/or with co-planar objects that are very close together. To achieve the circumstance you describe, the angle between the two triangles would have to be extremely small, very close to 0.0. And they have to be so far away that the depth buffer lacks sufficient precision to deal with it.

So, in order for the phenomenon you describe to even be possible, you would need for all three of these to be the case:

  1. Turning off backface culling.
  2. Two triangles that share an edge, with the angle between those triangles being extremely small.
  3. The shared edge being very far away from the camera.

This intersection of models and rendering state is vanishingly uncommon.

$\endgroup$
5
  • $\begingroup$ (1) is correct but irrelevant—one cannot assume backface culling is enabled, and real GPUs do not require it to correctly handle this case. Your points (2) and (3) are incorrect: the angle in question here is actually pretty close to 180°, and the surface is close to the camera; this occurred in multiple pixels in a fairly "usual" rendering. Just like consistent raster rules end up being required for real scenes, even though they feel like a "too rare to happen" case, this is another numerical edge case that must be solved to get acceptable results. $\endgroup$
    – geometrian
    Commented Mar 11, 2021 at 5:00
  • $\begingroup$ @imallett: "this occurred in multiple pixels in a fairly "usual" rendering" Prove it. Show me a circumstance where you wouldn't naturally turn off backface culling, you're looking at the scene with a reasonable perspective matrix and from a distance where numerical depth buffer precision, on a 24-bit depth buffer, would cause this problem. $\endgroup$ Commented Mar 11, 2021 at 7:10
  • $\begingroup$ There is no special handling in "real GPUs". The confluence of circumstances needed for this to take place just doesn't happen. $\endgroup$ Commented Mar 11, 2021 at 7:11
  • $\begingroup$ As I said previously, whether or not backface culling is enabled, as it would be in a production application, is immaterial because real GPUs handle this case correctly regardless. As for proving it—that's really disingenuous, but I mean, I have a scenefile. This came up in testing. This absolutely happens—with my 32-bit depth buffer and everything. The difference image with an OpenGL ground truth has only that one pixel different. $\endgroup$
    – geometrian
    Commented Mar 11, 2021 at 15:12
  • $\begingroup$ Look—easy example: put one silhouette vertex / edge of a model exactly on a pixel center. Voilà—the depths for all triangles at that sample are the same! The angles don't matter. Your depth precision and projection matrix don't matter. The same depth value is actually the "right" answer, even. Even besides any empirical evidence, do you legitimately believe that that case never, ever happens? This case already has to be handled by raster rules for computing sample coverage! $\endgroup$
    – geometrian
    Commented Mar 11, 2021 at 15:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.