RenderKit / embree

Embree ray tracing kernels repository.
Apache License 2.0
2.37k stars 389 forks source link

Barycentric (u,v) coords are too large by a factor of 2 when using RTC_SCENE_ROBUST. #52

Closed lbarbieri closed 9 years ago

lbarbieri commented 9 years ago

Hello,

I'm using the Embree Ray Tracing Kernels v2.6.1 and believe I've found a bug.

I'm seeing rtcIntersect() return different (u,v) hit coordinates for the same ray depending upon whether or not I created the scene with RTC_SCENE_ROBUST, and when ray.dir contains at least one zero component (and intersects the scene).

When RTC_SCENE_ROBUST not is used, the values are correct and map to a point very close to that calculated via "ray.org + ray.dir * ray.tfar". However when RTC_SCENE_ROBUST is used, the (u,v) values are too large by a factor of about 2, and so they map to some far away point.

I've duplicated this using both rtcIntersect() and rtcIntersect8().

Details about how / what I'm running:

Embree Ray Tracing Kernels 2.6.1 (Aug 12 2015) Compiler : GCC 4.9.2 Platform : Linux (64bit) CPU : Unknown CPU (GenuineIntel) ISA : SSE SSE2 SSE3 SSSE3 SSE41 SSE42 POPCNT AVX F16C RDRAND AVX2 FMA3 LZCNT BMI1 BMI2 MXCSR : FTZ=1, DAZ=1 Config : Release SSE4.1 SSE4.2 AVX AVX2 internal_tasking_system intersection_filter bufferstride

I'm happy to provide more info if needed.

Thanks! Lou

cbenthin commented 9 years ago

Hi Lou, Thanks for reporting this issue. There seems to be a problem with some new robust ray triangle intersection code that we introduced recently. I'll look into this. Do you have an example scene + camera settings by any chance so that I could reproduce the issue quickly?

Thanks.

lbarbieri commented 9 years ago

Thanks for your response.

Below is a hacked-together program that shoots a ray into a plane. If it's given any argument (doesn't matter what), then it creates the scene using RTC_SCENE_ROBUST and duplicates the issue; otherwise it does not use RTC_SCENE_ROBUST and so the issue doesn't occur. It prints the hit point as calculated using both tfar and (u,v), and also the distance between them.

Here are example runs with output from my machine, where the first run causes the issue and the second does not:

$ testBary 1
rayOrg=(0.1, 1.09482, 29.8984), rayDir(0, 0.99482, -0.101655)
Distance between intersection point calculated using barycentric coords vs. tfar is 707.15:
    tfar=588.339, hitTfar=(0.1, 586.386, -29.9092), u=0.587281, v=0.412699, hitBary=(-499.981, 87.3008, 0)

$ testBary 
rayOrg=(0.1, 1.09482, 29.8984), rayDir(0, 0.99482, -0.101655)
Distance between intersection point calculated using barycentric coords vs. tfar is 0.000139784:
    tfar=294.116, hitTfar=(0.1, 293.688, 1.14441e-05), u=0.293588, v=0.206312, hitBary=(0.100067, 293.688, 0)

And here's the code (uses c++11 standard):

// Pull in intrinsics, e.g. SSE, AVX, other SIMD support.
#ifdef _MSC_VER
#   include <intrin.h>
#else
#   include <x86intrin.h>
#endif

// Included per Embree documentation, but should be pulled-in by the above "*intrin.h" header.
#include <xmmintrin.h>      // SSE instructions.
#include <pmmintrin.h>      // SSE3 instructions.

#include <iostream>
#include <sstream>
#include <limits>
#include <string>
#include <vector>

#include <embree2/rtcore.h>
#include <embree2/rtcore_ray.h>

// SIMD-aware vector.
#include "math/vec3.h"      // embree/common
#include "math/vec3fa.h"    // embree/common

int main(int argc, char* argv[])
{
    //! Type representing a point in 3D space.  Aligned to 16-bit boundaries per Embree documentation.
    struct alignas(16) Vertex
    {
        float x, y, z;
        float unused;   //!< Exists only for purposes of alignment and binary compatibility with the Embree ray class.
    };

    //! Type representing a triangle in 3D space.  Fields are indicies into a Vertex array.
    struct Triangle
    {
        int32_t v0, v1, v2;
    };

    //! Vertices that make up the geometry primatives.
    std::vector<Vertex> m_vertices;

    //! Indices into m_vertices that make up triangle primitives.
    std::vector<Triangle> m_triangles;

    //! Defines a plane.
    const std::string m_geometry("1000x1000");

    // Embree initialization.
    {
        // Do this before rtcInit(), per Embree documentation.
        _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
        _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);

        rtcInit();
    }

    // Setup geometry.
    {
        // Format is "LengthxWidth".
        std::istringstream ss(m_geometry);
        float length = 0, width = 0;
        char by = 0;

        ss >> length >> by >> width;

        if (ss)
        {
            // Create in XY plane with center at origin.
            m_vertices.resize(4);

            m_vertices[0] = {-length / 2.f, -width / 2.f, 0};
            m_vertices[1] = { length / 2.f, -width / 2.f, 0};
            m_vertices[2] = { length / 2.f,  width / 2.f, 0};
            m_vertices[3] = {-length / 2.f,  width / 2.f, 0};

            m_triangles.resize(2);

            m_triangles[0] = {0, 1, 2};
            m_triangles[1] = {2, 3, 0};
        }
        else
        {
            throw std::invalid_argument("Bad geometry for plane object \"" + m_geometry + "\" - should be LengthxWidth.");
        }
    }

    // Setup scene.  Use RTC_SCENE_ROBUST if any argument is given.
    const RTCScene mainSceneId = rtcNewScene(RTC_SCENE_STATIC | (argc > 1 ? RTC_SCENE_ROBUST : RTCSceneFlags(0))/* | RTC_SCENE_HIGH_QUALITY*/, RTC_INTERSECT1);
    {
        const unsigned int id = rtcNewTriangleMesh(mainSceneId, RTC_GEOMETRY_STATIC, m_triangles.size(), m_vertices.size());

        rtcSetBuffer(mainSceneId, id, RTC_VERTEX_BUFFER, m_vertices.data(), 0, sizeof(Vertex));
        rtcSetBuffer(mainSceneId, id, RTC_INDEX_BUFFER, m_triangles.data(), 0, sizeof(Triangle));

        rtcCommit(mainSceneId);
    }

    // Shoot a ray into the plane.
    {
        RTCRay ray = {};

        ray.org[0] = 0.1f;
        ray.org[1] = 1.09482f;
        ray.org[2] = 29.8984f;

        ray.dir[0] = 0.f;
        ray.dir[1] = 0.99482f;
        ray.dir[2] = -0.101655f;

        ray.tnear = 0.05f;
        ray.tfar = std::numeric_limits<decltype(ray.tfar)>::infinity();
        ray.mask = -1;

        ray.geomID = RTC_INVALID_GEOMETRY_ID;
        ray.primID = RTC_INVALID_GEOMETRY_ID;
        ray.instID = RTC_INVALID_GEOMETRY_ID;

        rtcIntersect(mainSceneId, ray);

        if (ray.geomID == RTC_INVALID_GEOMETRY_ID) throw std::logic_error("Ray is supposed to intersect the scene!");

        const Triangle &triangle = m_triangles[ray.primID];

        // Our vertex representation.
        const Vertex &v0_ = m_vertices[triangle.v0];
        const Vertex &v1_ = m_vertices[triangle.v1];
        const Vertex &v2_ = m_vertices[triangle.v2];

        // Convert to fast, SIMD-aware vector implementation.
        const embree::Vec3fa v0(v0_.x, v0_.y, v0_.z);
        const embree::Vec3fa v1(v1_.x, v1_.y, v1_.z);
        const embree::Vec3fa v2(v2_.x, v2_.y, v2_.z);

        // Hit point given by Barycentric coords.
        embree::Vec3fa hitBary = v0 + ray.u * (v1 - v0) + ray.v * (v2 - v0);

        const embree::Vec3fa rayOrg = embree::Vec3fa(ray.org[0], ray.org[1], ray.org[2]);
        const embree::Vec3fa rayDir = embree::normalize(embree::Vec3fa(ray.dir[0], ray.dir[1], ray.dir[2]));

        // Hit point given by tfar.
        const embree::Vec3fa hitTfar = rayOrg + ray.tfar * rayDir;

        const embree::Vec3fa delta = hitBary - hitTfar;
        const float distance = embree::length(delta);

         std::cout << "rayOrg=" << rayOrg << ", rayDir" << rayDir << "\n"
             << "Distance between intersection point calculated using barycentric coords vs. tfar is " << distance << ":\n"
             << "    tfar=" << ray.tfar << ", hitTfar=" << hitTfar << ", u=" << ray.u << ", v=" << ray.v << ", hitBary=" << hitBary << std::endl;
    }

    return 0;
}

EDIT: I just noticed tfar changes too, so added it to the output along with the ray origin & direction.

Hope that helps...

cbenthin commented 9 years ago

Thanks for sharing the code. I've run your code on my Haswell-based machine using the latest Embree 2.6.1, and I get the following results:

Robust:

rayOrg=(0.1, 1.09482, 29.8984), rayDir(0, 0.99482, -0.101655) Distance between intersection point calculated using barycentric coords vs. tfar is 8.2909e-05: tfar=294.116, hitTfar=(0.1, 293.688, 1.14441e-05), u=0.293588, v=0.206312, hitBary=(0.0999451, 293.688, 0)

Default:

rayOrg=(0.1, 1.09482, 29.8984), rayDir(0, 0.99482, -0.101655) Distance between intersection point calculated using barycentric coords vs. tfar is 0.000139784: tfar=294.116, hitTfar=(0.1, 293.688, 1.14441e-05), u=0.293588, v=0.206312, hitBary=(0.100067, 293.688, 0)

Results looking good to me as there's no major difference in u, v, distance between the robust and default mode. However, I've used the Intel Compiler so there could still be an issue with GCC 4.9 here. I'll try to install this particular gcc version on my machine. Would be great if you could try to run your code with the binary version of Embree 2.6.1 which is compiled per default with the Intel compiler.

Thanks, Carsten

cbenthin commented 9 years ago

I've compiled Embree today with GCC 4.8.3 and get the same wrong results back that you observed. So it seems that there's an issue with the GCC generated code. I'll have a look at it ASAP.

Thanks.

cbenthin commented 9 years ago

The GCC/AVX2 issues should be fixed in the 'devel' branch and will be available in the next Embree release.

lbarbieri commented 9 years ago

I had a chance to pull it down and confirm - worked great!

Thanks for the fix! Lou