lighttransport / nanort

NanoRT, single header only modern ray tracing kernel.

Repository from Github https://github.comlighttransport/nanortRepository from Github https://github.comlighttransport/nanort

Possible accuracy problem

Gjacquenot opened this issue · comments

I want to report a potential bug, with a minimal complete program to illustrate my findings.

I have tested nanort with doubles.
I have noticed that a slight modification (1e-16) on direction can lead nanort to miss intersection.
In this demo program, I have a single triangle with a single ray. Intersection is obvious and should be detected with float or double: we hit a point inside the triangle (not on edges). Other raytracer (embree, vtk) have found intersection with float and double.

@syoyo Could you please have a look at this program?

  • Called without argument, we run a normal case where a ray creates an intersection on a single triangle
  • Called with one argument, we slightty changes the direction and have a no intersection result, that is not normal

Here is the program:

// g++ -O0 -std=c++11 -Wall main_bug_1.cpp  -o main_bug_1
#include "nanort.h"
#include <iostream>

typedef double real;

int main(int argc, char * argv[])
{
    std::cout << "This program exposes a possible accuracy bug with nanort." << std::endl<<std::flush;
    std::cout << "A slight variation on direction[0] makes nanort misses a real intersection." << std::endl<<std::flush;
    std::cout << "Called without argument, we run a normal case where a ray creates an intersection on a single triangle" << std::endl<<std::flush;
    std::cout << "Called with one argument, we slightty changes the direction and have a no intersection result, that is not normal" << std::endl<<std::flush;
    bool activate_precision_bug = false;
    if (argc>1)
    {
        activate_precision_bug = true;
    }
    real vertices[9];
    unsigned int triangles[3] ={0,1,2};

    const real xMin=-1.0, xMax=+1.0;
    const real zMin=-3.0, zMax=+3.0;
    vertices[3 * 0] = xMax; vertices[3 * 0 + 1] = 2.0; vertices[3 * 0 + 2] = zMin;
    vertices[3 * 1] = xMin; vertices[3 * 1 + 1] = 2.0; vertices[3 * 1 + 2] = zMin;
    vertices[3 * 2] = xMax; vertices[3 * 2 + 1] = 2.0; vertices[3 * 2 + 2] = zMax;

    real origins[3];
    real directions[3];

    origins[0] = -0.36; origins[1] = +7.93890843; origins[2] = 1.2160368;
    directions[1] = -8.66025404e-01; directions[2] = -0.5;
    directions[0] = 0.0;
    if (activate_precision_bug)
    {
        directions[0] = -5.30287619e-17;
    }
    std::cout << "directions[0] = " << directions[0] << std::endl;

    nanort::BVHBuildOptions<real> build_options; // Use default option
    nanort::TriangleMesh<real> triangle_mesh(vertices, triangles, sizeof(real) * 3);
    nanort::TriangleSAHPred<real> triangle_pred(vertices, triangles, sizeof(real) * 3);
    nanort::BVHAccel<real> accel;
    build_options.cache_bbox = true;
    int ret = accel.Build((size_t) 1, triangle_mesh, triangle_pred, build_options);
    assert(ret);
    nanort::Ray<real> ray;
    nanort::TriangleIntersector<real, nanort::TriangleIntersection<real> > triangle_intersector(vertices, triangles, sizeof(real) * 3);
    nanort::TriangleIntersection<real> isect;

    ray.org[0] = origins[0];
    ray.org[1] = origins[1];
    ray.org[2] = origins[2];


    const real length = sqrt(directions[0] * directions[0] +
                             directions[1] * directions[1] +
                             directions[2] * directions[2]);
    ray.dir[0] = directions[0]/length;
    ray.dir[1] = directions[1]/length;
    ray.dir[2] = directions[2]/length;

    ray.min_t = 0.0;
    ray.max_t =  1.0e+30;

    const bool hit = accel.Traverse(ray, triangle_intersector, &isect);
    if (hit)
    {
        std::cout << "We have the expected result" << std::endl<<std::flush;
        std::cout << "Intersection isect.u =" << isect.u << " v = " << isect.v << std::endl<<std::flush;
    }
    else
    {
        std::cout << "No intersection detected" << std::endl<<std::flush;
        std::cout << "We get the wrong result" << std::endl<<std::flush;

    }
    return 0;
}

At least I can reproduce an issue. I will investigate!

Thanks for your time. I have reproduced it on:

  • Windows with g++ and MSVC.
  • MacOSX with g++/clang

It seems also that if we replace double with float, bug also appears (but make no sense since below float precision)

I am not an expert with template class, but I have weird to have 1.0f constant in functions/methods where we use double template. Maybe a lead to follow...

Again thanks for your time

There was an unexpected offset value added to ray direction : 7490e9b#diff-6ae3ef545df31bcbb8d6f3814304e805L2029 and this was the source of the issue.

And also removed some float precision constant value.

Now fixed and I got expected result(your test code is added to test/regression/possible-accuracy-problem-30

Wow, that was fast... I suspected it right, but had no clue that we should use static_cast to fix the problem. Thanks a lot!

Yeah! Recent compiler shoud generate immediate value for static_cast(CONSTANT_VALUE) at compile time optimization so no performance slowdown! > we should use static_cast

I have changed to use template specialization for IntersectRayAABB 206543c#diff-6ae3ef545df31bcbb8d6f3814304e805R1900 This will give more precise intersection result for double type

Hello @syoyo , I have tried to run the regression test I have proposed with g++8 and clang++ on the latest version. I thought it was solved, but the bug reappeared. Could you please try yourself?

$ make -f Makefile.dev
clang++ -I../../../ -std=c++11 -fsanitize=address -g -O0 -o bug main.cc

$ ./bug
This program exposes a possible accuracy bug with nanort.
A slight variation on direction[0] makes nanort misses a real intersection.
Called without argument, we run a normal case where a ray creates an intersection on a single triangle
Called with one argument, we slightty changes the direction and have a no intersection result, that is not normal
directions[0] = 0
We have the expected result
Intersection isect.u =0.68 v = 0.131201

$ ./bug 1
This program exposes a possible accuracy bug with nanort.
A slight variation on direction[0] makes nanort misses a real intersection.
Called without argument, we run a normal case where a ray creates an intersection on a single triangle
Called with one argument, we slightty changes the direction and have a no intersection result, that is not normal
directions[0] = -5.30288e-17
No intersection detected
We get the wrong result

I can reproduce bug 1.

After brief investigation, ray-AABB test fails for very small value(around or less than double precision epsilon which is 2.2e-16). I think ray-AABB test is not robust or fails for denormal number. I need more investigation.

Oh, its my fault. Simply I forgot to add sign when returning inf in vsafe_inverse(). Now fixed in this commit: 61309ec
and regression test should pass.

Thanks a lot @syoyo ! It worked!

$ ./bug.exe 1
This program exposes a possible accuracy bug with nanort.
A slight variation on direction[0] makes nanort misses a real intersection.
Called without argument, we run a normal case where a ray creates an intersection on a single triangle
Called with one argument, we slightty changes the direction and have a no intersection result, that is not normal
directions[0] = -5.30288e-017
We have the expected result
Intersection isect.u =0.68 v = 0.131201