将鼠标坐标错误地转换为OpenGL

问题描述 投票:0回答:1

我正在尝试使用鼠标在球体表面拾取顶点。我正在使用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;
        }
    }
}

视觉结果

如您所见,选择的实际点是从光标位置偏移的。

enter image description here

这是显示错误的视频:

https://gfycat.com/graciousantiquechafer

很明显,z值有问题,我可以看到,当您偏离中心轴时,误差会变大。但我似乎无法找出确切的问题在代码中的什么位置。

c++ opengl linear-algebra projection freeglut
1个回答
0
投票

在“透视投影”中,投影矩阵描述了从针孔相机看到的世界3D点到视口的2D点的映射。观看量是一个平截头体(截顶的金字塔),金字塔的顶部是观看者的位置。如果从相机位置开始射线,则射线上的所有点都将具有相同的xy窗口坐标(和xy归一化的设备坐标)。

这意味着您的假设是错误的,观察方向不是(0,0,-1)

// Create directional vector pointing into the screen.
glm::vec3 into_screen = glm::vec3(0, 0, -1);

要找到“命中”当前鼠标位置的射线,您必须计算射线上的2个点。找到射线与近平面(深度= 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
    );

    // [...]
}
© www.soinside.com 2019 - 2024. All rights reserved.