将鼠标坐标转换为OpenGL时出现错误偏移

4

我正在尝试使用鼠标在球面上选择顶点。 我正在使用freeglut和OpenGL 4.5。

渲染

显示函数:

void display(void) {

    // Clear display port
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    // Reset camera
    gluLookAt(
        0.0, 0.0, 5.0,
        0.0, 0.0, 0.0,
        0.0, 1.0, 0.0
    );

    // Rotate
    glRotatef(-theta, 1.0, 0.0, 0.0);
    glRotatef(phi, 0.0, 0.0, 1.0);

    // Zoom
    glScalef(zoom, zoom, zoom);

    // Render sphere
    glPushMatrix();
    glTranslatef(0, 0, 0);
    glColor3f(1, 0, 0);
    glutWireSphere(1, 32, 32);
    glPopMatrix();

    .
    .
    .

    // Render selection point
    glPushMatrix();
    pointIsOnSphere ? glColor3f(0, 1, 0) : glColor3f(0, 0, 1);
    pointIsOnSphere ? glPointSize(5.0f) : glPointSize(2.5f);
    glBegin(GL_POINTS);
    glVertex3f(clickPosition.x, clickPosition.y, clickPosition.z);
    glEnd();
    glPopMatrix();

    // Swap buffers
    glutSwapBuffers();
}

重塑函数:

void reshape(GLsizei width, GLsizei height) {
    if (height == 0) {
        height = 1;
    }
    float ratio = 1.0 * width / height;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glViewport(0, 0, width, height);
    gluPerspective(45, ratio, 0.5, 5);
    glMatrixMode(GL_MODELVIEW);
}

鼠标操作

// Handles mouse click for point selection.
void mouse(int button, int state, int x, int y) {

    mouse_x = x;
    mouse_y = y;

    if (!pointIsOnSphere)
        return;

    if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
        controlPoints.push_back(clickPosition);

    if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
        RIGHT_BUTTON_DOWN = true;

    glutPostRedisplay();
}

// Handles mouse movement for rotation.
void motion(int x, int y) {
    if (RIGHT_BUTTON_DOWN) {
        phi += (float)(x - mouse_x) / 4.0; 
        theta += (float)(mouse_y - y) / 4.0;
    }

    mouse_x = x;
    mouse_y = y;
    glutPostRedisplay();
}

    // Handles mouse movement for point selection.
void passiveMotion(int x, int y) {
    // Get position of click.
    clickPosition = GetOGLPos(x, y);
    // Set click position's z position to camera's z position.
    clickPosition.z = 1;
    // Create directional vector pointing into the screen.
    glm::vec3 into_screen = glm::vec3(0, 0, -1);
    // Create ray.
    ray r = ray(
        clickPosition,
        into_screen
    );

    mousePosition = clickPosition;
    float t = hit_sphere(glm::vec3(0), 1, r);
    if (t != -1) {
        pointIsOnSphere = true;
        clickPosition += t * into_screen;
    }
    else {
        pointIsOnSphere = false;
    }

    glutPostRedisplay();
}

// Handles scroll input for zoom.
void mouseWheel(int button, int state, int x, int y) {
    if (state == 1) {
        zoom = zoom + zoom_sensitivity >= zoom_max ? zoom_max : zoom + zoom_sensitivity;
    }
    else if (state == -1) {
        zoom = zoom - zoom_sensitivity <= zoom_min ? zoom_min : zoom - zoom_sensitivity;
    }
    glutPostRedisplay();
}

3D拾取

// Get position of click in 3-d space
glm::vec3 GetOGLPos(int x, int y)
{
    GLint viewport[4];
    GLdouble modelview[16];
    GLdouble projection[16];
    GLfloat winX, winY, winZ;
    GLdouble posX, posY, posZ;

    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);

    winX = (float)x;
    winY = (float)viewport[3] - (float)y;
    glReadPixels(x, int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);

    gluUnProject(winX, winY, winZ, modelview, projection, viewport, &posX, &posY, &posZ);
    return glm::vec3(posX, posY, posZ);
}

float hit_sphere(const glm::vec3& center, float radius, const ray& r) {
    glm::vec3 oc = r.origin() - center;
    float a = dot(r.direction(), r.direction());
    float b = 2.0 * glm::dot(oc, r.direction());
    float c = glm::dot(oc, oc) - radius * radius;
    float discriminant = b * b - 4 * a*c;

    if (discriminant < 0.0) {
        return -1.0;
    }
    else {
        float numerator = -b - sqrt(discriminant);
        if (numerator > 0.0) {
            return numerator / (2.0 * a);
        }

        numerator = -b + sqrt(discriminant);
        if (numerator > 0.0) {
            return numerator / (2.0 * a);
        }
        else {
            return -1;
        }
    }
}

可视化结果

正如您所见,实际的选点位置与光标的位置偏离。

以下是显示该错误的视频:

https://gfycat.com/graciousantiquechafer

显然,z值出现了问题,我可以看到当您远离中心轴时,错误变得更加严重。但我似乎无法确定代码中确切的问题所在。

新错误

现在,这些点出现在球体表面之外。

enter image description here


1
这是一个线框图。深度缓冲区在黑色区域中未设置(为1)。 - Rabbid76
哦,糟糕。我看的是“被动运动”代码,它并没有…… - user253751
1个回答

4
在透视投影中,投影矩阵描述了从针孔相机看到的世界上的三维点到视口的二维点的映射。查看体是一个截锥体(一个截断的金字塔),其中金字塔的顶部是观察者的位置。如果您从相机位置开始发射一条光线,则光线上的所有点都具有相同的xy窗口坐标(和xy归一化设备坐标),这些点只是具有不同的“深度”(z坐标)。视线在视口上的投影是一个点。
这意味着您的假设是错误的,“进入屏幕”的方向不是(0,0,-1):
// Create directional vector pointing into the screen.
glm::vec3 into_screen = glm::vec3(0, 0, -1);
请注意,这适用于正交投影,但对于透视投影是错误的。方向取决于窗口坐标。幸运的是它不取决于深度,窗口坐标由鼠标位置给出。要找到“命中”当前鼠标位置的光线,必须计算光线上的两个点。找到光线与近平面(深度=0.0)和远平面(深度=1.0)的交点。这意味着光线上2个点的窗口坐标,该光线从相机位置开始,并通过鼠标光标。
point 1: (mouseX, height-mouseY, 0.0)
point 2: (mouseX, height-mouseY, 1.0)

修改 GetOGLPos 函数:

glm::vec3 GetOGLPos(int x, int y, float depth)
{
    GLint viewport[4];
    GLdouble modelview[16];
    GLdouble projection[16];
    GLfloat winX, winY, winZ;
    GLdouble posX, posY, posZ;

    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);

    winX = (float)x;
    winY = (float)viewport[3] - (float)y;
    winZ = depth;
    //glReadPixels(x, int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);

    gluUnProject(winX, winY, winZ, modelview, projection, viewport, &posX, &posY, &posZ);

    return glm::vec3(posX, posY, posZ);
}

定义光线(ray)

void passiveMotion(int x, int y)
{
    glm::vec3 clickPositionNear = GetOGLPos(x, y, 0.0);
    glm::vec3 clickPositionFar = GetOGLPos(x, y, 1.0);

    glm::vec3 into_screen = glm::normalize(clickPositionFar - clickPositionNear);

    ray r = ray(
        clickPositionNear,
        into_screen
    );

    // [...]
}


你的回答非常有帮助。我能够在我的光标上正确显示点,但是这种更改破坏了我的球体相交算法。现在,我的 hit_sphere() 函数仅返回 -1。你比我的代码有什么不同?或者说,我做错了什么? - Sam Fischer
我已经更新了我的代码,使用的是目前的版本。 - Sam Fischer
在调试过程中,我发现在我的 hit_sphere 函数中,numerator 数量总是负数,这就是为什么它总是返回 -1 的原因。但是为什么现在突然会出现这种情况呢? - Sam Fischer
1
顺便说一下,将问题更改为您的实际代码是一个不好的主意。实际上,这会使我的答案变得无用,因为它回答了一个对其他用户不再可见的问题。 - Rabbid76
1
@SamFischer 窗口坐标的第三个分量必须是深度,范围在[0.0, 1.0]之间,而不是近平面和远平面!请使用0.0和1.0代替NEAR_PLANEFAR_PLANE - Rabbid76

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