How can I avoid this corruption on depth-sorted se

2020-04-30 02:56发布

问题:

void setup() {
  size(600, 480, P3D);hint(ENABLE_DEPTH_SORT);
}

void draw()
{
  background(0); 
  translate(width/2, height/2);  fill(color(255,255,255),84);
  strokeWeight(0);
  translate(-40,0,1);sphere(80);
  translate(2*40,0,0);sphere(80);
  // Fails with corruption: http://i.imgur.com/GW2h7Qv.png
}

Note: sphereDetail need not apply. sphereDetail(60) causes fail with loss of left overlap:

回答1:

To understand why this is happening, you have to understand what's going on under the hood.

Think in Triangles

When you call the sphere() function, Processing is actually drawing a bunch of triangles. You can see this if you run this simple program:

size(500, 500, P3D);
translate(width/2, height/2);
sphere(100);

In your code, you have chosen to not draw the outline, but Processing is still drawing a bunch of triangles to draw your sphere. Here is what it draws with sphereDetail(10):

The point is, you need to stop seeing spheres, and see the triangles that make up those spheres.

Depth Buffering

OpenGL (and therefore Processing) uses depth buffering to figure out when not to draw something that's behind something else.

I'm just going to copy a section of this article, which explains it much better than I can:

Consider this example of drawing two triangles, A and B:

If we draw B first, then A, the depth buffer will see that the new pixels from A are closer than the ones previously drawn by B, so it will draw them over the top. If we draw in the opposite order (A followed by B) the depth buffer will see that the pixels coming in from B are further away than the ones already drawn by A, so it will discard them. In either case we get the correct result: A is on top, with B hidden behind it.

When you're drawing an opaque sphere, you're really drawing a bunch of opaque triangles. Processing will draw these triangles in whatever order the sphere-to-triangle algorithm generated them. This works for opaque objects, since Processing will check the Z-buffer and then skip over triangles that are behind triangles that were already drawn. That's basically as smart as OpenGL gets out of the box.

The Problem with Transparency

Again, quoting the article:

But what if this geometry is alpha blended, so B is partially visible through the translucent A triangle? This still works if we draw B first, then A over the top, but not if we draw A followed by B. In that case, the depth buffer will get a pixel from B, and notice that it has already drawn a closer pixel from A, but it has no way to deal with this situation. It’s only choices are to draw the B pixel (which will give the wrong result, because it would be blending the more distant B over the top of the closer A, and alpha blending is not commutative) or it could discard B entirely. Not good!

The problem is, this doesn't work for transparent triangles. Remember that Processing is drawing the triangles in whatever order the algorithm generated them, and it's using the Z-buffer to decide when to skip over triangles that are behind already-drawn triangles. But we don't want it to skip over any triangles! We want to draw every triangle, because we want to be able to see the back triangles through the front triangles.

For example, if we draw a transparent gray sphere in front, and then we draw a red sphere behind it, we won't see the red sphere.

size(500, 500, P3D);
translate(width/2, height/2);
noStroke();

//front gray sphere
fill(255, 255, 255, 64);
sphere(100);

//back red sphere
translate(0, 0, -500);
fill(255, 0, 0, 64);
sphere(100);

I'm using entire spheres here because I'm lazy, but imagine this exact problem happening for the individual triangles in each sphere. This is why you see those artifacts in the gray sphere, for the same reason you can't see the red sphere behind the gray sphere. OpenGL draws the gray sphere first, but then it skips over the red sphere because it knows that it's behind the front sphere. This is the correct behavior for opaque objects, but we're complicating things by adding transparency.

Drawing Order Matters

If instead we draw the back red sphere before we draw the front gray sphere, then we see the red sphere through the gray sphere:

size(500, 500, P3D);
translate(width/2, height/2);
noStroke();

//back red sphere
translate(0, 0, -500);
fill(255, 0, 0, 64);
sphere(100);

//front gray sphere
translate(0, 0, 500);
fill(255, 255, 255, 64);
sphere(100);

Note that we still see artifacts within each sphere because of the triangle order problem. This is happening for the same reason we couldn't see the red sphere when we draw it after the gray sphere.

Now, to fix the issue with the triangles, drawing order matters. Exactly like you have to draw the front gray sphere after you draw the back red sphere, you have to draw the triangles from the back to the front, that way OpenGL doesn't skip over any that it determines are behind an already-drawn triangle. This is a huge pain in the neck.

Processing to the Rescue

Luckily, Processing has an option to do this for you. That's the hint(ENABLE_DEPTH_SORT) function. With this enabled, Processing will sort every triangle by the average Z position of its three vertices, and then draw them in that order. With this enabled, we can draw the back red sphere after the front gray sphere, and Processing will sort the triangles for us and draw them in the correct order before it gets to OpenGL:

size(500, 500, P3D);
hint(ENABLE_DEPTH_SORT);

translate(width/2, height/2);
noStroke();

//front gray sphere
fill(255, 255, 255, 64);
sphere(100);

//back red sphere
translate(0, 0, -500);
fill(255, 0, 0, 64);
sphere(100);

Notice that we can see the back red sphere, even though we're drawing it after the front gray sphere. Processing is intervening on our behalf and sorting the triangles before actually drawing them in OpenGL. This also fixes the artifacts within each sphere, since the triangles are sorted in each sphere, not just between spheres.

The Problem with Intersection

Remember that the triangles are sorted by the average Z position of their 3 vertices. This is a problem for triangles that intersect. This is the crux of your issue.

From the article, imagine that you have two triangles A and B that intersect like this:

There is no possible way to sort these triangles, because we need to draw the top part of B over A, but the bottom part of A over B. The only solution is to detect when this happens and split the triangles where they intersect, but that would be prohibitively expensive.

Which triangle should be drawn first? There isn't a single answer, but Processing is going to do its best and sort them according to the average Z position of their 3 vertices. This is causing the artifacts you're seeing.

We can see this in a simple interactive sketch:

void setup() {
  size(500, 500, P3D);
  hint(ENABLE_DEPTH_SORT);
  noStroke();
}

void draw() {
  background(128);
  translate(width/2, height/2);

  //gray sphere
  fill(255, 255, 255, 64);
  sphere(100);

  //red sphere
  translate(0, 0, mouseY-width/2);
  fill(255, 0, 0, 64);
  sphere(100);
}

Move your mouse up and down to move the red sphere front or back. As it intersects with the gray sphere, you get the artifacts because the triangles are intersecting.

(Ignore the artifacts around the mouse, that's a result of creating a gif.)

This is the cause of the artifacts you're seeing.

Possible Solutions

What can you do to fix it? You've got a few options:

Option 1: Stop using transparency. Many of your issues are caused by using transparency, which makes things very complicated. The quickest and easiest thing to do would be to stop using transparency, as OpenGL works best with opaque objects.

Option 2: Stop using shapes that intersect. You can think of your intersecting spheres as impossible objects, which OpenGL doesn't handle very well. Could you simply move your shapes so they aren't intersecting? If you're trying to build a complex shape, maybe use a 3D model or construct it yourself using the vertex() function?

Option 3: Come up with your own sorting algorithm. Processing uses the average Z position, which isn't guaranteed to be correct when you have intersecting shapes. Instead of relying on Processing to sort the triangles, you could do it yourself. How you'd do that goes beyond this question, and it sounds super annoying, but that's the nature of 3D rendering.

Option 4: If you really, really, really need to have intersecting transparent shapes, then the only correct thing to do is to detect intersections and then split them up into subshapes that you then draw in the correct order. This is very very very annoying, but you're going down an annoying path.

You might also investigate shaders. I don't really know anything about shaders, but anytime I see an issue with 3D rendering, somebody inevitably comes along and says the answer is to use a shader. So that might be worth looking into (it also might not be worth looking into, I honestly don't know), although you're on your own with that one.



标签: processing