• [Maxim07]中光线与三角形求交算法的推导


    "Ray-Triangle Intersection Algorithm for Modern CPU Architectures" Maxim Shevtsov, Alexei Soupikov and Alexander Kapustin (2007) 中的公式推导时的约定与我通常使用的约定不一致,并且叙述并不严格,故自己推导如下:

    约定

    以下公式均定义在右手系中。

    * 表示数乘或点乘(内积),x表示叉乘(外积)

    光线(射线)的参数表示为 x = o + d * t (t > 0) (1)

    其中x为光线上任意一点,o为光线起点,d为光线方向,t为参数。

    三角形的参数表示为 x = (1 - u - v) * p + u * p1 + v * p2 (0 <= u <= 1, 0 <= v <= 1) (2)

    x为三角形内的任一点,p, p1, p2为三角形的三个顶点位置,u, v为参数,(1 - u - v, u, v)称为三角形的重心坐标(barycentric coordinates)。

    为了方便,令e1 = p1 - p, e2 = p2 - p

    我们约定p, p1, p2按顺序构成逆时针的方向为正面,即n = e1 x e2

     

    推导

    光线与三角形的交点可以表示为线性方程 (1 - u - v) * p + u * p1 + v * p2 = o + d * t (3)

    e1 * u + e2 * v - d * t = o - p (4)

    K = -d, L = o - p, 则原式变为 e1 * u + e2 * v + K * t = L (5)

    利用Cramer's rule解该线性方程得

    t = dett / det, u = detu / det, v = detv / det (6)

    其中

    det = K * (e1 x e2) = - d * n (7)

    dett = L * (e1 x e2) = (o - p) * n (8)

    detu = K * (L x e2) = -d * [(o - p) x e2] (9)

    detv = K * (e1 x L) = -d * [e1 x (o - p)] (10)

    其实到这里,已经求出了判断交点所需的信息,当然基于以上公式,我们还会预计算一些信息,储存在三角形中,减少求交所需的计算量。

    事实上,我们可以在公式(6)的分子分母同除以一个数,所得的值不变,利用这一点,我们同除以nw,w是n的最大分量的轴向,令u, v为另外两个轴向,且u < v。

    det / nw = -(du * nu + dv * nv + dw) (11)

    dett / nw = (ou * nu + ov * nv + ow) - (pu * nu + pv * nv + pw) (12)

    detu / nw = d * [(p - o) x e2] = d * [p x e2 - o x e2] = du * (pv * e2w - pw * e2v - ov * e2w + ow * e2v) + dv * (pw * e2u - pu * e2w - ow * e2u + ou * e2w) + dw * (pu * e2v - pv * e2u - ou * e2v + ov * e2u) (13)

    利用e1 * n = 0, e2 * n = 0我们可以得到

    e1w = - (e1u * nu + e1v * nv) (14)

    e2w = - (e2u * nu + e2v * nv)

    利用公式(11),我们可以得到

    dw = -(du * nu + dv * nv + det / nw) (15)

    将公式(14), (15)代入(13)中消去e2w, dw并整理出e2u, e2v得

    detu / nw = e2v * [du * (ou - pu) * nu + du * (ov - pv) * nv + dw * (ow - pw) - (pu - ou) * det / nw] - e2u * [dv * (ou - pu) * nu + dv * (ov - pv) * nv + dv * (ow - pw) - (pv - ov) * det / nw] (16)

    Du = du * dett / nw - (pu - ou) * det, Dv = dv * dett / nw - (pv - ov) * det代入(16)得

    detu / nw = e2v * Du - e2u * Dv (17)

    同理,实际上我们不需要计算,只需要观察公式(9)与公式(10)的区别,把e2换成e1并加一负号得

    detv / nw = e1u * Dv - e1v * Du (18)

    至此,我们已经推导出了整个算法中需要用到的公式,下节给出算法的代码。

    实现

    实现中我们为每个三角形预计算并储存如下数据结构,正好是48 Byte大小,可以对齐到16 Byte边界:

    三角形数据结构
    struct TriAccel {
        
    uint w;
        
    uint nw;
        
    float np;        
        
    float nu;
        
    float nv;
        
    float pu;
        
    float pv;
        
    float e1u;
        
    float e2v;
        
    float e2u;
        
    float e1v;
        
    uint tri_id;
    };

    预计算三角形代码如下,为了处理背面裁剪增加了一些代码,不多介绍了:

    预计算三角形
    TriAccel::TriAccel(const Vector3f & v1_pos, const Vector3f & v2_pos, const Vector3f & v3_pos)
    {
        Vector3f e1;
        Vector3f e2;
        Vector3f n;
        Vector3f absn;
        
    float maxVal;
        
    float inv_nw;
        
    uint u, v;

        sub(e1, v2_pos, v1_pos);
        sub(e2, v3_pos, v1_pos);
        cross(n, e1, e2);
        absn.x 
    = absf(n.x);
        absn.y 
    = absf(n.y);
        absn.z 
    = absf(n.z);

        
    if (absn.x > absn.y) {
            u 
    = Y_AXIS;
            v 
    = Z_AXIS;
            w 
    = X_AXIS;
            maxVal 
    = absn.x;
        } 
    else {
            u 
    = Z_AXIS;
            v 
    = X_AXIS;
            w 
    = Y_AXIS;
            maxVal 
    = absn.y;
        }
        
    if (absn.z > maxVal) {
            u 
    = X_AXIS;
            v 
    = Y_AXIS;
            w 
    = Z_AXIS;
        }

        inv_nw 
    = 1.0f / n.arr[w];
        mulvf(n, inv_nw);

        nu 
    = n.arr[u];
        nv 
    = n.arr[v];

        pu 
    = v1_pos.arr[u];
        pv 
    = v1_pos.arr[v];

        np 
    = n.arr[u] * v1_pos.arr[u] + n.arr[v] * v1_pos.arr[v] + v1_pos.arr[w];

        e1u 
    = (e1.arr[u] * inv_nw);
        e1v 
    = (e1.arr[v] * inv_nw);

        e2u 
    = (e2.arr[u] * inv_nw);
        e2v 
    = (e2.arr[v] * inv_nw);

        
    if (inv_nw >= 0.0f)
        {
            nw 
    = 0x00000000;
        }
        
    else
        {
            nw 
    = 0x80000000;
        }
    }

    最后是光线与三角形求交代码:

    光线与三角形求交 
    #define FLOAT_TO_DWORD(x) (*(unsigned long*)(&(x)))

    #define FLOAT_BITWISE_XOR(dest, lhs, rhs) \
        FLOAT_TO_DWORD(dest) 
    = (FLOAT_TO_DWORD(lhs)) ^ (FLOAT_TO_DWORD(rhs))

    #define FLOAT_BITWISE_OR(dest, lhs, rhs) \
        FLOAT_TO_DWORD(dest) 
    = (FLOAT_TO_DWORD(lhs)) | (FLOAT_TO_DWORD(rhs))

    #define FLOAT_MUL_NEGATIVE(lhs, rhs) \
        ((FLOAT_TO_DWORD(lhs) 
    ^ FLOAT_TO_DWORD(rhs)) & 0x80000000== 0x80000000
     
    static const uint sAxisTable[3][2= {
        {Y_AXIS, Z_AXIS}, 
        {Z_AXIS, X_AXIS}, 
        {X_AXIS, Y_AXIS}, 
    };

    bool TriAccel::intersect(const Vector3f & ray_src, const Vector3f & ray_dir, const float t_near, const float t_far, float & t, Vector3f & bary)
    {
        
    float det;
        
    float dett;
        
    float Du;
        
    float Dv;
        
    float detu;
        
    float detv;
        
    float tempdet0;
        
    float tempdet1;
        
    uint u, v;

        u 
    = sAxisTable[w][0];
        v 
    = sAxisTable[w][1];

        det 
    = -(ray_dir.arr[u] * nu + ray_dir.arr[v] * nv + ray_dir.arr[w]);

        
    if (FLOAT_MUL_NEGATIVE(det, nw))
        {
            
    return false;
        }

        dett 
    = (ray_src.arr[u] * nu + ray_src.arr[v] * nv + ray_src.arr[w]) - np;

        
    if (FLOAT_MUL_NEGATIVE(det, dett))
        {
            
    return false;
        }

        Du 
    = ray_dir.arr[u] * dett - (pu - ray_src.arr[u]) * det;
        Dv 
    = ray_dir.arr[v] * dett - (pv - ray_src.arr[v]) * det;
        detu 
    = e2v * Du - e2u * Dv;
        detv 
    = e1u * Dv - e1v * Du;

        tempdet0 
    = det - detu - detv;

        FLOAT_BITWISE_XOR(tempdet0, tempdet0, detu);
        FLOAT_BITWISE_XOR(tempdet1, detv, detu);
        FLOAT_BITWISE_OR(tempdet0, tempdet0, tempdet1);

        
    if ((FLOAT_TO_DWORD(tempdet0) & 0x80000000== 0x80000000)
        {
            
    return false;
        }

        
    float inv_det = 1.0f / det;

        t 
    = dett * inv_det;
        
    if (t > t_far || t < t_near)
        {
            
    return false;
        }

        bary.y 
    = detu * inv_det;
        bary.z 
    = detv * inv_det;
        bary.x 
    = 1.0f - (bary.y + bary.z);

        
    return true;
    }

    到此该睡觉去鸟……文中如有错漏之处,欢迎指正,谢谢!

  • 相关阅读:
    《软件工程课程总结》
    课后作业-阅读任务-阅读笔记-4
    两个不同的网络,进行数据同步的设计思路
    IDEA清理缓存项目
    IDEA快捷键
    IDEA如何添加多个maven项目工程
    sqlserver数据库中的mdf文件太大,表空间分析和表空间释放
    关于AOP的面向切面编程
    关于.net进行爬虫
    关于调用别人接口的一些问题总结
  • 原文地址:https://www.cnblogs.com/len3d/p/1783365.html
Copyright © 2020-2023  润新知