2

我已经使用 C++ 和 OpenGL 设置了一个简单的 3d 第一人称演示,它似乎工作得相当好。我的目标是:当用户将相机指向一个平面并单击鼠标左键时,我想绘制一条从玩家位置指向相机所面对方向的射线与该平面的交点。

因此,我从两个向量Vector position和开始Vector rotation,其中向量是一个非常标准的三维向量类:

class Vector
{
  public:
    GLfloat x, y, z;

    Vector() {};

    Vector(GLfloat x, GLfloat y, GLfloat z)
    {
      this->x = x;
      this->y = y;
      this->z = z;
    }

    GLfloat dot(const Vector &vector) const
    {
      return x * vector.x + y * vector.y + z * vector.z;
    }

    ... etc ...

并且Plane p,Plane 是一个存储平面法线和 d 的简单结构。我直接从 Christer Ericson 的“实时碰撞检测”一书中复制了这个结构:

struct Plane 
{
  Vector n; // Plane normal. Points x on the plane satisfy Dot(n,x) = d
  float d; // d = dot(n,p) for a given point p on the plane
};

首先,我将position其作为射线的起点,我称之为a. 我使用那个点rotation来找到射线的终点,b. 然后我使用一种算法从同一本书中找到射线和平面的交点。实际上,我自己也实现了相同的方法,但我在这里直接使用书中的代码,以确保我没有搞砸任何事情:

void pickPoint()
{
    const float length = 100.0f;

    // Points a and b
    Vector a = State::position;
    Vector b = a;

    // Find point b of directed line ab
    Vector radians(Math::rad(State::rotation.x), Math::rad(State::rotation.y), 0);
    const float lengthYZ = Math::cos(radians.x) * length;

    b.y -= Math::sin(radians.x) * length;
    b.x += Math::sin(radians.y) * lengthYZ;
    b.z -= Math::cos(radians.y) * lengthYZ;

    // Compute the t value for the directed line ab intersecting the plane
    Vector ab = b - a;

    GLfloat t = (p.d - p.n.dot(a)) / p.n.dot(ab);

    printf("Plane normal: %f, %f, %f\n", p.n.x, p.n.y, p.n.z);
    printf("Plane value d: %f\n", p.d);
    printf("Rotation (degrees): %f, %f, %f\n", State::rotation.x, State::rotation.y, State::rotation.z);
    printf("Rotation (radians): %f, %f, %f\n", radians.x, radians.y, radians.z);
    printf("Point a: %f, %f, %f\n", a.x, a.y, a.z);
    printf("Point b: %f, %f, %f\n", b.x, b.y, b.z);
    printf("Expected length of ray: %f\n", length);
    printf("Actual length of ray: %f\n", ab.length());
    printf("Value t: %f\n", t);

    // If t in [0..1] compute and return intersection point
    if(t >= 0.0f && t <= 1.0f) 
    {
        point = a + t * ab;
        printf("Intersection: %f, %f, %f\n", point.x, point.y, point.z);
    }
    // Else no intersection
    else
    {
        printf("No intersection found\n");
    }

    printf("\n\n");
}

当我用 OpenGL 渲染这个点时,它看起来非常接近光线和平面的交点。但是通过打印出实际值,我发现对于特定的位置和旋转,交点最多可以偏离 0.000004。这是交点不准确的示例 - 我知道交点不在平面上,因为它的 Y 值应该是 0,而不是 0.000002。我也可以将其分回平面方程并得到一个不等式:

Plane normal: 0.000000, 1.000000, 0.000000
Plane value d: 0.000000
Rotation (degrees): 70.100044, 1.899823, 0.000000
Rotation (radians): 1.223477, 0.033158, 0.000000
Point a: 20.818802, 27.240383, 15.124892
Point b: 21.947229, -66.788452, -18.894285
Expected length of ray: 100.000000
Actual length of ray: 100.000000
Value t: 0.289702
Intersection: 21.145710, 0.000002, 5.269455

现在,我知道浮点数只是实数的近似值,所以我猜测这种不准确性只是浮点舍入的影响,尽管我可能在代码的其他地方犯了错误。我知道交叉点只偏离了非常小的量,但我仍然关心它,因为我计划使用这些点通过将它们捕捉到任意方向的网格来定义模型或关卡的顶点,所以我实际上想要这些点在那个网格上,即使它们有点不准确。这可能是一种被误导的方法——我真的不知道。

所以我的问题是:这种不准确性只是工作中的浮点舍入,还是我在其他地方犯了错误?

如果只是浮点舍入,有什么办法处理吗?我尝试以各种方式舍入旋转和位置向量的值,这显然会导致交点不太准确,但有时我仍然会得到不在平面上的交点。我确实阅读了一个类似问题的答案(这个平面射线相交代码是否正确?),其中提到保持尺寸很大,但我不确定这到底意味着什么。

抱歉,如果之前有人问过这个问题 - 我搜索过,但我没有看到任何我遇到的麻烦。谢谢!

4

1 回答 1

1

你的数学似乎是正确的,这绝对看起来像一个舍入错误。我有一种强烈的感觉,就是这条线:

GLfloat t = (p.d - p.n.dot(a)) / p.n.dot(ab);

也就是说,我没有看到任何其他计算 t 的方法。您可以通过在 printf 语句中使用“%.12f”(或更多)来验证是否丢失了精度。查明罪魁祸首的另一种方法是尝试逐步进行 t 计算并沿途打印结果以查看是否在某处丢失了精度。

如果精度对您来说真的那么重要,您是否尝试过使用双精度浮点?

于 2013-02-27T02:56:30.630 回答