如何进行射线平面相交?

26

如何计算射线和平面之间的交点?

代码

这会产生错误的结果。

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 定义了该平面。


3
当你说这个不起作用时,具体是什么没有起作用?它崩溃了吗?结果错误吗?编译失败了吗? - Matt Coubrough
2
你的法向量是否保证指向远离射线起点?否则,denom < 0 很可能仍会产生一个交点。 - wonce
4个回答

39

有评论指出,你也希望允许分母为负数,否则您将错过与平面前面的交点。但是,您仍然希望进行测试以避免零除法,这表明光线与平面平行。在计算 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;

1
什么是中心和什么是光线起点? - Lily
1
这里的 center 是平面上已知的一个点。这使用了平面的“点-法向量”描述方式。 - Joan Charmant
5
我显然在谈论C ++,因为问题标记为C ++。我假设你正在谈论std :: abs()(它具有不同的重载),而不是abs()(只处理整数)。由于答案中包含abs()而不是std :: abs(),并且也没有使用using指令,所以我认为这是一个错误。有关更多信息,请参见此处:https://dev59.com/K2Ei5IYBdhLWcg3wUa86 - Tara
1
@Duckdoom5,我希望你能删除那条评论。它是错误的,会给新手带来错误的信息。 - Glenn Maynard
1
@GlennMaynard 您是正确的,我假设这里使用了 std::abs(),所以我会删除该注释。 - Duckdoom5
显示剩余3条评论

8

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

一般来说,我们会用射线的参数方程与几何图形的隐式方程相交。

假设有一个射线方程 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 值。以下为 Maxima 中的示例:

enter image description here

请注意,答案看起来像是两个点积的商!平面的法向量是平面方程的前三个系数 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);
}

我似乎找不到有关射线隐式公式的更多信息。在x(t)中,a、t和a0代表什么?谢谢! - brain56
光线以参数形式表示,而平面以隐式形式表示。 请参阅http://www.cs.umd.edu/~djacobs/CMSC427/RayTracing.pdf - vwvan
2
那个紧挨着 t =- 符号几乎看不见。 - Mateen Ulhaq

5

数学

定义:

  • 令射线通过参数方程表示为 q = p + t*v,其中p为起点,v为方向向量,对于 t >= 0 成立。

  • 令平面是满足方程式 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形式提供一个完整的工作示例。此外还讨论了如果我们只想反射光线到一个正常定向的平面上(即平面的前面而不是后面)应该怎么做。 - Mateen Ulhaq
此外,查看此答案,获取 Snell 法向量形式的推导和参数 t 的解决方案。 - Mateen Ulhaq
t = - (点积(n, p0) + d) / 点积(n, v) - KeithB

4
实现vwvan的回答
Vector3 Intersect(Vector3 planeP, Vector3 planeN, Vector3 rayP, Vector3 rayD)
{
    var d = Vector3.Dot(planeP, -planeN);
    var t = -(d + Vector3.Dot(rayP, planeN)) / Vector3.Dot(rayD, planeN);
    return rayP + t * rayD;
}

1
你为什么在$t$的计算中写出了两个点积,而不是调用$Vector3.Dot(rayP, planeN)$? - The Coding Wombat
@TheCodingWombat 我过去四年很忙:D - Bas Smit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接