19

EDIT: I have now solved the problem; you can see my solution in the answers.

I'm in the process of writing a realtime raytracer using OpenGL (in a GLSL Compute Shader), and I've run into a slight problem with some of my line-triangle intersections (or at least, I believe they are the culprit). Here's a picture of what's happening:

Spheres in a room with some artifacts

As you can see some pixels are being coloured black at the intersection of two triangles near the top of the image. It's probably got something to do with the way I'm handling floats or something, and I've tried searching for a solution online but can't find similar situations. Perhaps there's an important keyword I'm missing?

Anyways, the important piece of code is this one:

#define EPSILON 0.001f
#define FAR_CLIP 10000.0f
float FindRayTriangleIntersection(Ray r, Triangle p)
{
    // Based on Moller-Trumbone paper
    vec3 E1 = p.v1 - p.v0;
    vec3 E2 = p.v2 - p.v0;
    vec3 T = r.origin - p.v0;
    vec3 D = r.dir;
    vec3 P = cross(D, E2);
    vec3 Q = cross(T, E1);

    float f = 1.0f / dot(P, E1);
    float t = f * dot(Q, E2);
    float u = f * dot(P, T);
    float v = f * dot(Q, D);

    if (u > -EPSILON && v > -EPSILON && u+v < 1.0f+EPSILON) return t;
    else return FAR_CLIP;
}

I've tried various values for EPSILON, tried variations with +/- for the EPSILON values, but to no avail. Also, changing the 1.0f+EPSILON to a 1.0-EPSILON yields a steady black line the whole way across.

Also to clarify, there definitely is NOT a gap between the two triangles. They are tightly packed (and I have also tried extending them so they intersect, but I still get the same black dots).

Curiously enough, the bottom intersection shows no sign of this phenomenon.

Last note: if more of my code is needed just ask and I'll try to isolate some more code (or maybe just link to the entire shader).

UPDATE: It was pointed out that the 'black artifacts' are in fact brown. So I've dug a bit deeper and turned off all reflections, and got this result:

Spheres without reflections

The brown colour is actually coming from just the copper material on the top, but more importantly I think I have an idea what the cause of the problem is, but I'm no closer to solving it.

It seems that when the rays get fired out, due to very slight imperfections in the floating arithmetic, some rays intersect the top triangle, and some intersect the bottom.

So I suppose now the question reduces to this: how can I have some sort of consistency in deciding which triangle should be hit in cases like this?

Michael Oliver
  • 1,392
  • 8
  • 22
  • The pixels aren't black, they're dark brown. The problem is unlikely your ray-triangle intersection code. – Soonts Feb 02 '14 at 06:28
  • Thanks, that was very helpful toward a solution, I hadn't realized. I've updated my post with some further information. – Michael Oliver Feb 02 '14 at 07:49
  • Try to change your EPSILON to 0.0f - what will happen? – Soonts Feb 02 '14 at 08:33
  • I get a ton of noise everywhere in the scene, as reflecting rays start self-intersecting the objects they are leaving (meaning pointlight visibility gets fudged, as well as any reflections). – Michael Oliver Feb 02 '14 at 08:37
  • But you just turned off the reflections, didnt you? – Soonts Feb 02 '14 at 08:38
  • I did, but when a ray intersects an object, it still needs to generate a new ray to determine if any lights make the point visible. This new ray is essentially a reflection, and without the `EPSILON` it sometimes just self-intersects the object. I can update with another picture if you like. – Michael Oliver Feb 02 '14 at 08:40
  • 1
    My point is the 1 px thick line where you see artifacts is the area where the ray intersect both triangles due to your epsilon value. Then, you somehow choose random triangle to display. – Soonts Feb 02 '14 at 08:40
  • To confirm, you can as well increase epsilon to e.g. 0.005 or decrease to 0.00002, if this will alter the thickness of your error line, you'll know what to check next. – Soonts Feb 02 '14 at 08:42
  • Your second-last comment actually led me to the solution, so I've updated my post yet again. Thanks so much! – Michael Oliver Feb 02 '14 at 08:51
  • Yep, developing numerically stable code is hard. BTW, I do not think you have solved this problem: just change your scene moving your second object by EPSILON closer to your camera, and you should encounter the same problem again. – Soonts Feb 02 '14 at 09:00
  • You're right, it did just as you suspect. However I was able to get away with setting the `EPSILON` to 0.0001f, which I think is reasonable. If I ever have a scene where two triangles are in fact that close together, then there will be artifacts, but I think I can deal with that. If you can think of a good way to handle this situation that would be great, though I'm fairly happy at this point. – Michael Oliver Feb 02 '14 at 09:16
  • You could detect such situations (when for a given pixels, there’s a second-nearest triangle that’s within epsilon from the nearest one), and blend the colors of those triangles together. – Soonts Feb 03 '14 at 06:12
  • If one distance is T1, another T2 so that T1 < T2 and T2-T1 – Soonts Feb 03 '14 at 06:12
  • I've implemented what you described, and it does a pretty good job. Especially with some anti-aliasing, it's nearly undetectable. Thanks again! – Michael Oliver Feb 03 '14 at 08:03
  • You’re welcome. And one more minor improvement. I would try to choose E based on the T1, i.e. E = someSmallValue*T1. Due to the nature of the floating-points, absolute error is proportional to the value. That’s why with variable E value you’ll probably get slightly more consistent result between the edges that are close to the camera, and edges that are far away near the FAR_CLIP plane. – Soonts Feb 03 '14 at 08:51
  • Also, plz post ur pix :-) – Soonts Feb 03 '14 at 08:52
  • Could you please add your solution as an answer? This is easier for people browsing the site. :) Your question also showed up in my weekly newsletter in the 'Can you answer these?' section, while it shouldn't have. – lesderid Feb 04 '14 at 22:56
  • I'm planning to post my solution with pictures later today after work. Sorry about that! – Michael Oliver Feb 05 '14 at 15:41

1 Answers1

13

So it turns out it was not the code I had posted that caused the problem. Thanks to some help in the comment, I was able to find it was this code when I'm determining the nearest object to the camera:

float nearest_t = FAR_CLIP;
int nearest_index = 0;
for (int j=0; j<NumObjects; j++)
{
    float t = FAR_CLIP;
    t = FindRayObjectIntersection(r, objects[j]);

    if (t < nearest_t && t > EPSILON && t < FAR_CLIP)
    {
        nearest_t = t;
        nearest_index = j;
    }
}

When determining t, sometimes the triangles were so close together that the t < nearest_t had an almost probabilistic result, since the intersections were roughly the same distance from the camera.

My initial solution was to change the inner if-statement to:

if (t < nearest_t-EPSILON && t > EPSILON && t < FAR_CLIP)

This ensures that if two intersections are very close together, it will always choose the first object to display (unless the second object is closer by at least EPSILON). Here is a resulting image (with reflections disabled):

Spheres

Now there were still some small artifacts, so it was clear that there was still a slight problem. So upon some discussion in the comments, @Soonts came up with the idea of blending the triangles' colours. This lead me to have to change the above code further in order to keep track of the distance to both triangles:

if (t > EPSILON && t < FAR_CLIP && abs(nearest_t - t) < EPSILON)
{
    nearest_index2 = nearest_index;
    nearest_t2 = nearest_t;
}
if (t < nearest_t+EPSILON && t > EPSILON && t < FAR_CLIP)
{
    nearest_t = t;
    nearest_index = j;
}

I also added this colour blending code:

OverallColor = mix(c1, c2, 0.5f * abs(T1 - T2) / EPSILON);

After these two steps, and honestly I think this effect was more from the logic change than the blending change, I got this result:

Spheres

I hope others find this solution helpful, or at the very least that it sparks some ideas to solve your own problems. As a final example, here is the beautiful result with reflections, softer shadows and some anti-aliasing all turned on:

Happy raytracing

Happy raytracing!

Michael Oliver
  • 1,392
  • 8
  • 22