5

I am trying to implement the ray tracing algorithm and I have some trouble computing the reflected rays of spherical objects.It seems that for some particular rays, the reflected ray just passes through and is collinear with the traced ray. Bellow is how i record the ray - sphere intersection:

bool Sphere::intersectLocal(const ray & r, isect & i) const {
    Vec3d P = r.getPosition();
    Vec3d D = r.getDirection();
    //D.normalize();
    double a = dot(D, D);
    double b = 2 * dot(P, D);
    double c = dot(P, P) - 1;
    double delta = b * b - 4 * a * c;
    if (delta < 0)
        return false;
    if (delta == 0) {
        double t = -b / 2 * a;
        Vec3d Q = P + t * D;
        Vec3d N = Q;
        N.normalize();
        i.setT(t);
        i.setN(N);
        i.setObject(this);
        return true;
    }
    if (delta > 0) {
        double t1 = (-b - sqrt(delta)) / 2 * a;
        double t2 = (-b + sqrt(delta)) / 2 * a;
        double t;
        if (t1 > 0) t = t1;
        else if (t2 > 0) t = t2;
        else return false;
        Vec3d N = P + t * D;
        N.normalize();
        i.setT(t);
        i.setN(N);
        i.setObject(this);
        return true;
    }
    return false;
}

And this is how I compute the reflected ray for each intersection:

isect i;
if (scene - > intersect(r, i)) {
    // An intersection occured!  
    const Material & m = i.getMaterial();
    double t = i.t;
    Vec3d N = i.N;
    Vec3d I = m.shade(scene, r, i); //local illumination
    if (!m.kr(i).iszero() && depth >= 0) {
        // compute reflection direction
        Vec3d raydir = r.getDirection();
        Vec3d refldir = 2 * dot(-raydir, i.N) * i.N + raydir;
        refldir.normalize();
        ray reflectionRay = ray(r.at(i.t), refldir, ray::RayType::REFLECTION);
        Vec3d reflection = traceRay(reflectionRay, thresh, depth - 1);
        Vec3d R = reflection;
        I += R;
    }
    return I;
} else {
    // No intersection.  This ray travels to infinity, so we color
    // it according to the background color, which in this (simple) case
    // is just black.
    return Vec3d(0.0, 0.0, 0.0);
}

The code above seems to work fine for most of the points on the sphere where the rays intersect, but for others it does not reflect as i expected

enter image description here

enter image description here

enter image description here

genpfault
  • 51,148
  • 11
  • 85
  • 139
itma96
  • 87
  • 6
  • 2
    the images looks like you got some accuracy problem somewhere. Did you try to use doubles instead of floats? Here few links for comparison and help: [Reflection and refraction impossible without recursive ray tracing?](https://stackoverflow.com/a/45140313/2521214) and [ray and ellipsoid intersection accuracy improvement](https://stackoverflow.com/q/25470493/2521214) also using camera relative coordinates usually improves accuracy. – Spektre Dec 17 '17 at 22:00
  • You are right, the problem has something to do with accuracy. I don't know for sure if it makes any difference between floats and doubles in terms of equality operations, but I found out that if I add a small offset to the intersection point along the reflection direction, the problem goes away. See here the result: https://imgur.com/a/as5wn. My guess is that if i take the starting point of the reflected ray to be exactly the intersection point, the ray can sometimes intersect with the sphere instantly thus reflecting the ray inside the sphere. – itma96 Dec 17 '17 at 23:01
  • 1
    Yes you should add a small distance to new ray position after each intersection to avoid multiple self intersections. Got similar problems while debugging the ray-tracer I linked. Well ray circle intersection needs quadratic therms and with GPU floats the are not very precise ... especially if the magnitude of vectors is big.... doubles slows things down a bit but makes much nicer and stable result – Spektre Dec 17 '17 at 23:49
  • 3
    You guessed. This is a very common problem in raytracers that can occur while implementing shadows/reflection. More commonly referred to as "shadow-acne" (the black spots) The reason being as you and Spektre figured out, self intersection. Hence it is usually advised to add a small float constant like epsilon = 0.01f – gallickgunner Dec 18 '17 at 03:25
  • @wandering-warrior +1 heh didn't know it got an therm in English. I always refer to it as noise or artifacts :-) – Spektre Dec 18 '17 at 09:40
  • https://stackoverflow.com/questions/41211892/ray-tracer-artifacts-with-reflection/41232196#41232196 – Daniel A. Thompson Feb 01 '18 at 20:29

2 Answers2

0

If I see right, this makes the normal face same direction as the ray. So with ray==normal==reflected_ray nothing gets reflected.

Vec3d Q = P + t * D;
Vec3d N = Q;
Tom M
  • 2,815
  • 2
  • 20
  • 47
Neithy
  • 139
  • 4
0

About errors in floating-point arithmetic and how to deal with it:

What Every Computer Scientist Should Know About Floating-Point Arithmetic

Here you can find how to compare floating-point numbers. Searching for relative absolute compare floating you may find more information.

https://floating-point-gui.de/errors/comparison/

This is an excerpt from my code in C#. Almost never use absolute compare.

public static bool IsAlmostRelativeEquals(this double d1, double d2, double epsilon)
{
    double absDiff = Math.Abs(d1 - d2);

    if (double.IsPositiveInfinity(absDiff))
        return false;

    if (absDiff < epsilon)
        return true;

    double absMax = Math.Max(Math.Abs(d1), Math.Abs(d2));

    return Math.Abs(d1 - d2) <= epsilon * absMax;
}

public static bool IsAlmostZero(this double d, double epsilon)
{
    double abs = Math.Abs(d);

    if (double.IsPositiveInfinity(abs))
        return false;

    return abs < epsilon;
}