c++ - 当 OpenGL 相机处于低高度(低 Z 坐标)时,为什么将窗口坐标映射到球体很困难?

标签 c++ opengl graphics geometry hittest

我有一个球体(代表地球),我希望用户能够在我跟踪球体上鼠标位置下方的点时移动鼠标。

一切正常,只要相机距离球体表面的高度合理(例如,相当于现实生活中至少几百米)。

但是,如果我放大到离球体表面太近(比如,离球体表面 30 米),那么我会开始观察到一个奇怪的行为:我现在绘制的所有点似乎都开始“捕捉”到某个预定义的格子在空间中,如果我尝试在鼠标正下方的表面上的点处画几条线,它们会“捕捉”到附近的某个点,而不是光标下方。

具体来说,我使用以下代码将点从 3D 映射到 2D 并返回:

double line_sphere_intersect(double const (&o)[3], double const (&d)[3], double const r)
{
    double const
        dd = d[0] * d[0] + d[1] * d[1] + d[2] * d[2],
        od = o[0] * d[0] + o[1] * d[1] + o[2] * d[2],
        oo = o[0] * o[0] + o[1] * o[1] + o[2] * o[2],
        left = -od,
        right = sqrt(od * od - dd * (oo - r * r)),
        r1 = (left + right) / dd,
        r2 = (left - right) / dd;
    return ((r1 < 0) ^ (r1 < r2)) ? r1 : r2;
}

Point3D mouse_pos_to_coord(int x, int y)
{
    GLdouble model[16];  glGetDoublev(GL_MODELVIEW_MATRIX, model);
    GLdouble  proj[16];  glGetDoublev(GL_proj_MATRIX, proj);
    GLint view[4];       glGetIntegerv(GL_view, view);  
    y = view[3] - y;  // invert y axis

    GLdouble a[3]; if (!gluUnProject(x, y, 0       , model, proj, view, &a[0], &a[1], &a[2])) { throw "singular"; }
    GLdouble b[3]; if (!gluUnProject(x, y, 1 - 1E-4, model, proj, view, &b[0], &b[1], &b[2])) { throw "singular"; }
    for (size_t i = 0; i < sizeof(b) / sizeof(*b); ++i) { b[i] -= a[i]; }
    double const t = line_sphere_intersect(a, b, earth_radius / 1000);
    Point3D result = Point3D(t * b[0] + a[0], t * b[1] + a[1], t * b[2] + a[2]);
    Point3D temp;
    if (false /* changing this to 'true' changes things, see question */)
    {
        gluProject(result.X, result.Y, result.Z, model, proj, view, &temp.X, &temp.Y, &temp.Z);
        gluUnProject(temp.X, temp.Y, 1 - 1E-4, model, proj, view, &result.X, &result.Y, &result.Z);
        gluProject(result.X, result.Y, result.Z, model, proj, view, &temp.X, &temp.Y, &temp.Z);
    }
    return result;
}

使用以下矩阵:

glMatrixMode(GL_PROJECTION);
gluPerspective(
    60, (double)viewport[2] / (double)viewport[3], pow(FLT_EPSILON, 0.9),
    earth_radius_in_1000km * (0.5 + diag_dist / tune_factor / zoom_factor));
glMatrixMode(GL_MODELVIEW);
gluLookAt(eye.X, eye.Y, eye.Z, 0, 0, 0, 0, 1, 0);

eye 是地球上空的当前相机位置。

(是的,我到处都在使用 double,所以它不应该是 float 的精度问题。)

此外,我观察到,如果我将代码中的 if (false) 更改为 if (true),那么红线现在似乎直接相交在光标下方,我觉得很莫名其妙。 (编辑:我不确定映射点是否仍然正确,但是......我很难说。)

这意味着当 2D 光标位置的相应“Z”坐标(即相对于窗口)接近 1 时,红线正确相交...但是当它退化到大约0.9 或更低,然后我开始看到“捕捉”问题。

不过,我不明白这会如何或为什么会影响任何事情。为什么 Z 坐标会影响这样的事情?这是正常的吗?我该如何解决这个问题?

最佳答案

仅仅因为您使用的是 double 并不意味着您不会遇到精度问题。如果您使用大量数字,那么您可以不太精确地表示小的分数变化。维基百科有 a decent explanation of floating point precision :

Small values, close to zero, can be represented with much higher resolution (e.g. one femtometre) than large ones because a greater scale (e.g. light years) must be selected for encoding significantly larger values.

您可以尝试长加倍作为实验。如果您尝试这样做并且问题得到解决或至少有所改善,那么您就知道精度存在问题。

开始报告一些您可以比较的数字,而不是依赖图形表示。这也将消除作为问题来源的绘图代码。如果数字看起来正确,则可能是绘图代码出了问题,而不是交集计算出了问题。

对您的函数进行一些单元测试,以证明您在中间点获得了预期的数字。

关于c++ - 当 OpenGL 相机处于低高度(低 Z 坐标)时,为什么将窗口坐标映射到球体很困难?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24010060/

相关文章:

C++ 从 *.txt 文件读取数据到结构 vector

c# - 在这种情况下,为什么 .NET 比 C++ 快?

opengl - 是否可以在我的 OpenGL 程序中使用 Ubuntu 的文本渲染引擎?

c++ - 没有纹理图像单元的 Opengl 纹理

c++ - 您将如何实现黑白棋游戏? (奥赛罗)

c++ - 隐式类型转换看不到基类匹配。错误或功能?

c++ - (Re)Using std::algorithms with non-standard containers

opengl - 在 OpenGL 中渲染裁剪的图像

r - ggplot2:设置 alpha 值时,光栅绘图无法按预期工作

javascript - 二维游戏算法来计算子弹击中目标所需的速度?