C++ 快速SSE射线-4三角形相交

C++ 快速SSE射线-4三角形相交,c++,raytracing,C++,Raytracing,我目前正在开发一个路径跟踪器,我正在寻找优化光线三角形交点的方法。我目前使用Moller Trumbore算法的以下sse4实现: bool Ray::intersectTriangle(const Triangle tri, float& result) const { __m128 q = _mm_cross_ps(m_directionM128, tri.e2); float a = _mm_dp_ps(tri.e1, q, dotProductMask).m12

我目前正在开发一个路径跟踪器,我正在寻找优化光线三角形交点的方法。我目前使用Moller Trumbore算法的以下sse4实现:

bool Ray::intersectTriangle(const Triangle tri, float& result) const
{
    __m128 q = _mm_cross_ps(m_directionM128, tri.e2);

    float a = _mm_dp_ps(tri.e1, q, dotProductMask).m128_f32[0];

    if (a > negativeEpsilon && a < positiveEpsilon)
        return false;

    float f = 1.0f / a;

    __m128 s = _mm_sub_ps(m_originM128, tri.v0);
    float u = f * _mm_dp_ps(s, q, dotProductMask).m128_f32[0];

    if (u < 0.0f)
        return false;

    __m128 r = _mm_cross_ps(s, tri.e1);
    float v = f * _mm_dp_ps(m_directionM128, r, dotProductMask).m128_f32[0];

    if (v < 0.0f || (u + v > 1.0f))
        return false;

    float t = f * _mm_dp_ps(tri.e2, r, dotProductMask).m128_f32[0];
    if (t < 0.0f || t > m_length)
        return false;

    result = t;

    return true;
}
bool-Ray::相交三角形(常量三角形三、浮点和结果)常量
{
__m128 q=_mm_cross_ps(m_方向m128,tri.e2);
浮点a=_mm_dp_ps(tri.e1,q,dotProductMask).m128_f32[0];
如果(a>NegativeeSilon&&a1.0f))
返回false;
浮动t=f*_mm_dp_ps(tri.e2,r,dotProductMask).m128_f32[0];
如果(t<0.0f | | t>m|u长度)
返回false;
结果=t;
返回true;
}
(如果有人发现了优化它的方法,请告诉我)。 然后我读到,可以使用SIMD指令同时对4个三角形执行相交测试。但如何做到这一点呢?我不知道怎么可能以比我的顺序方式更有效的方式实现它


与我的渲染器有关的小代码。

使用AVX512最多可以生成16个三角形,使用AVX2最多可以生成8个三角形,使用SSE最多可以生成4个三角形。不过,诀窍是确保数据采用SOA格式。另一个技巧是在任何时候都不要“返回false”(只在最后过滤结果)。因此,三角形输入看起来像:

  struct Tri {
    __m256 e1[3];
    __m256 e2[3];
    __m256 v0[3];
  };
你的光线看起来像:

  struct Ray {
    __m256 dir[3];
    __m256 pos[3];
  };
然后,数学代码看起来就好得多了(请注意_mm_dp_ps并不是有史以来编写得最快的函数,也请注意访问u m128/u m256/u m512类型的内部实现是不可移植的)

方法中基本上有4个条件:

if (a > negativeEpsilon && a < positiveEpsilon)

if (u < 0.0f)

if (v < 0.0f || (u + v > 1.0f))

if (t < 0.0f || t > m_length)
虽然最终的代码可能看起来有点糟糕,但最终会比您的方法快得多。然而,魔鬼在于细节

这种方法的一个主要问题是,您可以选择对8个三角形测试1条光线,或者对1个三角形测试8条光线。对于主要的射线来说,这可能不是什么大问题。对于具有向不同方向散射习惯的二次射线,事情可能开始变得有点烦人。大多数光线跟踪代码很有可能最终遵循以下模式:测试->排序->批处理->测试->排序->批处理


如果你不遵循这个模式,你将永远无法充分利用向量单位。(谢天谢地,AVX512中的压缩/扩展指令在这方面帮助很大!)

我以以下工作代码结束

struct PackedTriangles
{
    __m256 e1[3];
    __m256 e2[3];
    __m256 v0[3];
    __m256 inactiveMask; // Required. We cant always have 8 triangles per packet.
};

struct PackedIntersectionResult
{
    float t = Math::infinity<float>();
    int idx;
};

struct PackedRay
{
    __m256 m_origin[3];
    __m256 m_direction[3];
    __m256 m_length;

    bool intersect(const PackedTriangles& packedTris, PackedIntersectionResult& result) const;
};

#define or8f _mm256_or_ps
#define mul _mm256_mul_ps
#define fmsub _mm256_fmsub_ps
#define fmadd _mm256_fmadd_ps
#define cmp _mm256_cmp_ps
#define div _mm256_div_ps

void avx_multi_cross(__m256 result[3], const __m256 a[3], const __m256 b[3])
{
    result[0] = fmsub(a[1], b[2], mul(b[1], a[2]));
    result[1] = fmsub(a[2], b[0], mul(b[2], a[0]));
    result[2] = fmsub(a[0], b[1], mul(b[0], a[1]));
}

__m256 avx_multi_dot(const __m256 a[3], const __m256 b[3])
{
    return fmadd(a[2], b[2], fmadd(a[1], b[1], mul(a[0], b[0])));
}

void avx_multi_sub(__m256 result[3], const __m256 a[3], const __m256 b[3])
{
    result[0] = _mm256_sub_ps(a[0], b[0]);
    result[1] = _mm256_sub_ps(a[1], b[1]);
    result[2] = _mm256_sub_ps(a[2], b[2]);
}

const __m256 oneM256 = _mm256_set1_ps(1.0f);
const __m256 minusOneM256 = _mm256_set1_ps(-1.0f);
const __m256 positiveEpsilonM256 = _mm256_set1_ps(1e-6f);
const __m256 negativeEpsilonM256 = _mm256_set1_ps(-1e-6f);
const __m256 zeroM256 = _mm256_set1_ps(0.0f);

bool PackedRay::intersect(const PackedTriangles& packedTris, PackedIntersectionResult& result) const
{
    __m256 q[3];
    avx_multi_cross(q, m_direction, packedTris.e2);

    __m256 a = avx_multi_dot(packedTris.e1, q);

    __m256 f = div(oneM256, a);

    __m256 s[3];
    avx_multi_sub(s, m_origin, packedTris.v0);

    __m256 u = mul(f, avx_multi_dot(s, q));

    __m256 r[3];
    avx_multi_cross(r, s, packedTris.e1);

    __m256 v = mul(f, avx_multi_dot(m_direction, r));

    __m256 t = mul(f, avx_multi_dot(packedTris.e2, r));

    // Failure conditions
    __m256 failed = _mm256_and_ps(
        cmp(a, negativeEpsilonM256, _CMP_GT_OQ),
        cmp(a, positiveEpsilonM256, _CMP_LT_OQ)
    );

    failed = or8f(failed, cmp(u, zeroM256, _CMP_LT_OQ));
    failed = or8f(failed, cmp(v, zeroM256, _CMP_LT_OQ));
    failed = or8f(failed, cmp(_mm256_add_ps(u, v), oneM256, _CMP_GT_OQ));
    failed = or8f(failed, cmp(t, zeroM256, _CMP_LT_OQ));
    failed = or8f(failed, cmp(t, m_length, _CMP_GT_OQ));
    failed = or8f(failed, packedTris.inactiveMask);

    __m256 tResults = _mm256_blendv_ps(t, minusOneM256, failed);

    int mask = _mm256_movemask_ps(tResults);
    if (mask != 0xFF)
    {
        // There is at least one intersection
        result.idx = -1;

        float* ptr = (float*)&tResults;
        for (int i = 0; i < 8; ++i)
        {
            if (ptr[i] >= 0.0f && ptr[i] < result.t)
            {
                result.t = ptr[i];
                result.idx = i;
            }
        }

        return result.idx != -1;
    }

    return false;
}
struct packedttriangles
{
__m256 e1[3];
__m256-e2[3];
__m256 v0[3];
__m256 inactiveMask;//必需。我们不能总是每个数据包有8个三角形。
};
结构PackedIntersectionResult
{
float t=数学::无穷大();
int-idx;
};
结构打包机
{
__m256 m_原点[3];
__m256米_方向[3];
__m256米长;
布尔相交(常数packedttriangles和packedTris,PackedIntersectionResult和result)常数;
};
#定义or8f\u mm256\u或\u ps
#定义多个mm256多个ps
#定义fmsub\u mm256\u fmsub\u ps
#定义fmadd\u mm256\u fmadd\u ps
#定义cmp\u mm256\u cmp\u ps
#定义div\u mm256\u div\u ps
无效avx_多重交叉(_m256结果[3],常数m256 a[3],常数m256 b[3])
{
结果[0]=fmsub(a[1],b[2],mul(b[1],a[2]);
结果[1]=fmsub(a[2],b[0],mul(b[2],a[0]);
结果[2]=fmsub(a[0],b[1],mul(b[0],a[1]);
}
__m256 avx_多点(常数m256 a[3],常数m256 b[3])
{
返回fmadd(a[2],b[2],fmadd(a[1],b[1],mul(a[0],b[0]));
}
void avx_multi_sub(_m256 result[3],const__m256a[3],const__m256b[3])
{
结果[0]=_mm256_sub_ps(a[0],b[0]);
结果[1]=_mm256_sub_ps(a[1],b[1]);
结果[2]=_mm256_sub_ps(a[2],b[2]);
}
常数m256 oneM256=_mm256_set1_ps(1.0f);
常数m256 minusOneM256=_mm256_set1_ps(-1.0f);
常数m256正定值为NM256=_mm256_set1_ps(1e-6f);
常数m256 NEgativeEEPSILONM256=\uMM256\uSET1\uPS(-1e-6f);
常数(0.0f);
bool PackedRay::intersect(const packedttriangles&packedTris,PackedIntersectionResult&result)const
{
__m256q[3];
avx_多重交叉(q,m_方向,packedTris.e2);
__m256 a=avx_多点(packedTris.e1,q);
__m256 f=div(一个m256,a);
__m256s[3];
avx_多功能sub(s、m_原点、packedTris.v0);
__m256 u=mul(f,avx_多点(s,q));
__m256r[3];
avx_多交叉(r、s、packedTris.e1);
__m256 v=mul(f,avx_多点(m_方向,r));
__m256 t=mul(f,avx_多点(packedTris.e2,r));
//失效条件
__m256失败=_mm256_和_ps(
cmp(a,NM256,_cmp_GT_OQ),
cmp(a、NM256、cmp、LT、OQ)
);
失败=或8F(失败,cmp(u,zeroM256,_cmp_LT_OQ));
失败=或8F(失败,cmp(v,zeroM256,_cmp_LT_OQ));
失败=或8F(失败,cmp(_mm256_add_ps(u,v),oneM256,_cmp_GT_OQ));
失败=或8F(失败,cmp(t,zeroM256,_cmp_LT_OQ));
失败=或8F(失败,cmp(t,m_长度,_cmp_GT_OQ));
失败=或8F(失败,packedTris.inactiveMask);
__m256结果=_mm256_blendv_ps(t,minusOneM256,失败);
int mask=_mm256_movemask_ps(tResults);
如果(掩码!=0xFF)
{
//至少有一个十字路口
result.idx=-1;
float*ptr=(float*)&tResults;
对于(int i=0;i<8;++i)
{
如果(ptr[i]>=0.0f&&ptr[i]
结果

结果非常棒。对于100k三角形场景我的加速比为84%!!。一段很长的时间
__m256 condition0 = (a > negativeEpsilon && a < positiveEpsilon);

__m256 condition1 = (u < 0.0f)

__m256 condition2 = (v < 0.0f || (u + v > 1.0f))

__m256 condition3 = (t < 0.0f || t > m_length)

// combine all conditions that can cause failure.
__m256 failed = or8f(or8f(condition0, condition1), or8f(condition2, condition3));
// if(failed) return -1;
// else return t;
return _mm256_blendv_ps(t, _mm256_set1_ps(-1.0f), failed);
struct PackedTriangles
{
    __m256 e1[3];
    __m256 e2[3];
    __m256 v0[3];
    __m256 inactiveMask; // Required. We cant always have 8 triangles per packet.
};

struct PackedIntersectionResult
{
    float t = Math::infinity<float>();
    int idx;
};

struct PackedRay
{
    __m256 m_origin[3];
    __m256 m_direction[3];
    __m256 m_length;

    bool intersect(const PackedTriangles& packedTris, PackedIntersectionResult& result) const;
};

#define or8f _mm256_or_ps
#define mul _mm256_mul_ps
#define fmsub _mm256_fmsub_ps
#define fmadd _mm256_fmadd_ps
#define cmp _mm256_cmp_ps
#define div _mm256_div_ps

void avx_multi_cross(__m256 result[3], const __m256 a[3], const __m256 b[3])
{
    result[0] = fmsub(a[1], b[2], mul(b[1], a[2]));
    result[1] = fmsub(a[2], b[0], mul(b[2], a[0]));
    result[2] = fmsub(a[0], b[1], mul(b[0], a[1]));
}

__m256 avx_multi_dot(const __m256 a[3], const __m256 b[3])
{
    return fmadd(a[2], b[2], fmadd(a[1], b[1], mul(a[0], b[0])));
}

void avx_multi_sub(__m256 result[3], const __m256 a[3], const __m256 b[3])
{
    result[0] = _mm256_sub_ps(a[0], b[0]);
    result[1] = _mm256_sub_ps(a[1], b[1]);
    result[2] = _mm256_sub_ps(a[2], b[2]);
}

const __m256 oneM256 = _mm256_set1_ps(1.0f);
const __m256 minusOneM256 = _mm256_set1_ps(-1.0f);
const __m256 positiveEpsilonM256 = _mm256_set1_ps(1e-6f);
const __m256 negativeEpsilonM256 = _mm256_set1_ps(-1e-6f);
const __m256 zeroM256 = _mm256_set1_ps(0.0f);

bool PackedRay::intersect(const PackedTriangles& packedTris, PackedIntersectionResult& result) const
{
    __m256 q[3];
    avx_multi_cross(q, m_direction, packedTris.e2);

    __m256 a = avx_multi_dot(packedTris.e1, q);

    __m256 f = div(oneM256, a);

    __m256 s[3];
    avx_multi_sub(s, m_origin, packedTris.v0);

    __m256 u = mul(f, avx_multi_dot(s, q));

    __m256 r[3];
    avx_multi_cross(r, s, packedTris.e1);

    __m256 v = mul(f, avx_multi_dot(m_direction, r));

    __m256 t = mul(f, avx_multi_dot(packedTris.e2, r));

    // Failure conditions
    __m256 failed = _mm256_and_ps(
        cmp(a, negativeEpsilonM256, _CMP_GT_OQ),
        cmp(a, positiveEpsilonM256, _CMP_LT_OQ)
    );

    failed = or8f(failed, cmp(u, zeroM256, _CMP_LT_OQ));
    failed = or8f(failed, cmp(v, zeroM256, _CMP_LT_OQ));
    failed = or8f(failed, cmp(_mm256_add_ps(u, v), oneM256, _CMP_GT_OQ));
    failed = or8f(failed, cmp(t, zeroM256, _CMP_LT_OQ));
    failed = or8f(failed, cmp(t, m_length, _CMP_GT_OQ));
    failed = or8f(failed, packedTris.inactiveMask);

    __m256 tResults = _mm256_blendv_ps(t, minusOneM256, failed);

    int mask = _mm256_movemask_ps(tResults);
    if (mask != 0xFF)
    {
        // There is at least one intersection
        result.idx = -1;

        float* ptr = (float*)&tResults;
        for (int i = 0; i < 8; ++i)
        {
            if (ptr[i] >= 0.0f && ptr[i] < result.t)
            {
                result.t = ptr[i];
                result.idx = i;
            }
        }

        return result.idx != -1;
    }

    return false;
}