【问题标题】:How to do ray plane intersection?射线平面相交怎么做?
【发布时间】:2014-07-21 10:48:58
【问题描述】:

如何计算射线与平面的交点?

代码

这会产生错误的结果。

float denom = normal.dot(ray.direction);

if (denom > 0)
{
    float t = -((center - ray.origin).dot(normal)) / denom;

    if (t >= 0)
    {
        rec.tHit = t;
        rec.anyHit = true;
        computeSurfaceHitFields(ray, rec);
        return true;
    }
}

参数

ray代表射线对象。
ray.direction是方向向量。
ray.origin是原点向量。
rec代表结果对象。
rec.tHit是命中的值。
rec.anyHit 是一个布尔值。

我的函数可以访问平面:
centernormal 定义平面

【问题讨论】:

  • 当你说这不起作用时,具体是什么不起作用?是不是崩溃了,结果有错吗?是不是编译失败了?
  • 您的法线向量是否保证指向远离射线原点?否则,denom 很可能仍然会产生交集。

标签: c++ raytracing


【解决方案1】:

正如 wonce 所说,您还希望分母为负数,否则您将错过与飞机正面的交叉点。但是,您仍然需要进行测试以避免除以零,这将表明光线平行于平面。您在计算t 时也有多余的否定。总的来说,它应该是这样的:

float denom = normal.dot(ray.direction);
if (abs(denom) > 0.0001f) // your favorite epsilon
{
    float t = (center - ray.origin).dot(normal) / denom;
    if (t >= 0) return true; // you might want to allow an epsilon here too
}
return false;

【讨论】:

  • 什么是中心,什么是光线原点?
  • center 这里是平面上的一个已知点。这使用平面的“点法线”描述。
  • 我必须指出你应该使用fabs()而不是abs(),因为abs()将输入参数转换为int
  • @Duckdoom5:我显然在谈论 C++,因为这个问题被标记为 C++。我假设您在谈论std::abs()(它有不同的重载)而不是abs()(它只处理整数)。由于答案包含 abs() 而不是 std::abs() 并且也不使用 using 指令,因此我认为这是一个错误。请参阅此处了解更多信息:stackoverflow.com/questions/21392627/…
  • @Duckdoom5 我希望你能删除那条评论。这是错误的,你会用错误的信息混淆新手。
【解决方案2】:

首先考虑射线平面相交的数学:

一般来说,光线的参数形式与几何的隐式形式相交。

所以给定一条射线,形式为 x = a * t + a0, y = b * t + b0, z = c * t + c0;

和一个平面的形式:A x * B y * C z + D = 0;

现在将 x、y 和 z 射线方程代入平面方程,您将得到 t 中的多项式。然后,您为 t 的实际值求解该多项式。使用这些 t 值,您可以将其代入射线方程以获得 x、y 和 z 的实际值。 这是在千里马:

请注意,答案看起来像是两个点积的商! 平面的法线是平面方程 A、B 和 C 的前三个系数。 您仍然需要 D 来唯一确定平面。 然后你用你选择的语言编写代码,如下所示:

Point3D intersectRayPlane(Ray ray, Plane plane)
{
    Point3D point3D;

    //  Do the dot products and find t > epsilon that provides intersection.


    return (point3D);
}

【讨论】:

  • 我似乎找不到更多关于射线的隐式公式。 a、t 和 a0 在 x(t) 中代表什么?谢谢!
  • 射线是参数形式,而平面是隐式形式。见cs.umd.edu/~djacobs/CMSC427/RayTracing.pdf
  • - 旁边的 t = 符号几乎不可见。
【解决方案3】:

vwvan 答案的实现

Vector3 Intersect(Vector3 planeP, Vector3 planeN, Vector3 rayP, Vector3 rayD)
{
    var d = Vector3.Dot(planeP, -planeN);
    var t = -(d + rayP.z * planeN.z + rayP.y * planeN.y + rayP.x * planeN.x) / (rayD.z * planeN.z + rayD.y * planeN.y + rayD.x * planeN.x);
    return rayP + t * rayD;
}

【讨论】:

  • 为什么在$t$计算中把两个点积全写出来,而不是调用$Vector3.Dot(rayP, planeN)$?
【解决方案4】:

数学

定义:

  • 让射线参数化地由q = p + t*v 为初始点p 和方向矢量vt >= 0

  • 设平面为点集r,满足等式dot(n, r) + d = 0 法向量n = (a, b, c) 和常数d。完全展开后,平面方程也可以写成我们熟悉的ax + by + cz + d = 0

q满足平面方程时,就会发生射线-平面相交。代入,我们有:

d = -dot(n, q)
  = -dot(n, p + t * v)
  = -dot(n, p) + t * dot(n, v)

重新排列:

t = -(dot(n, p) + d) / dot(n, v)

t 的这个值可用于通过将其插入p + t*v 来确定交叉点。

示例实现

std::optional<vec3> intersectRayWithPlane(
    vec3 p, vec3 v,  // ray
    vec3 n, float d  // plane
) {
    float denom = dot(n, v);

    // Prevent divide by zero:
    if (abs(denom) <= 1e-4f)
        return std::nullopt;

    // If you want to ensure the ray reflects off only
    // the "top" half of the plane, use this instead:
    //
    // if (-denom <= 1e-4f)
    //     return std::nullopt;

    float t = -(dot(n, p) + d) / dot(n, v);

    // Use pointy end of the ray.
    // It is technically correct to compare t < 0,
    // but that may be undesirable in a raytracer.
    if (t <= 1e-4)
        return std::nullopt;

    return p + t * v;
}

【讨论】:

  • 我写这个答案是为了为ax + by + cz + d = 0 表单提供一个完整的工作示例。另外讨论了如果我们只希望从一个正常定向的平面(即平面的前侧......而不是它的后侧)反射光线该怎么做。
  • 另外,参见this answer,了解向量形式的斯涅尔定律的推导,以及参数t 的解。
  • t = - (dot(n, p) + d) / dot(n, v) 应该是 t = - (dot(n, p0) + d) / dot(n, v)
猜你喜欢
  • 2012-02-14
  • 1970-01-01
  • 1970-01-01
  • 2014-06-04
  • 2011-08-05
  • 2015-05-22
  • 1970-01-01
  • 2011-05-25
  • 1970-01-01
相关资源
最近更新 更多