Qt OpenGL 屏幕坐标至世界坐标转化失败帮助

QOpenGLWidget 中求解屏幕坐标到世界坐标的变换不正确。

顶点着色器代码如下:

#version 330 core
layout (location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
 
void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

initializeGL()代码如下:

void MyGLWidget::initializeGL()
{
    initializeOpenGLFunctions();                // 初始化OpenGL函数

    mShader = new QOpenGLShaderProgram();
    if (!mShader->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/vertex.vert")) {     //添加并编译顶点着色器
        qDebug() << "ERROR:" << mShader->log();     //如果编译出错,打印报错信息
    }
    if (!mShader->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/fragment.frag")) {   //添加并编译片段着色器
        qDebug() << "ERROR:" << mShader->log();     //如果编译出错,打印报错信息
    }
    if (!mShader->link()) {                            //链接着色器
        qDebug() << "ERROR:" << mShader->log();     //如果链接出错,打印报错信息
    }

    mVao = new QOpenGLVertexArrayObject();
    mVbo = new QOpenGLBuffer(QOpenGLBuffer::Type::VertexBuffer);
    mVao->create();
    mVao->bind();
    mVbo->create();
    mVbo->bind();

    // 将顶点数据分配到VBO中 pra1-顶点数据 pra2-字节长度
    mVbo->allocate(vertices.data(), sizeof(float)*vertices.size());
    
    mShader->setAttributeBuffer(0, GL_FLOAT, 0, 3, sizeof(GLfloat) * 3);
    mShader->enableAttributeArray(0);

    mVbo->release();
    mVao->release();
}

paintGL()代码如下:

glClearColor(0.0f, 0.2f, 0.0f, 1.0f);                    // 清除颜色信息
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);        // 清除缓存/深度数据
    
    mShader->bind();

    // 模型矩阵 局部空间->世界空间
    QMatrix4x4 model;
    model.rotate(0, QVector3D(1.0f, 0.0f, 0.0f));
    mShader->setUniformValue("model", model);
    qDebug() << "Model = " << model;

    // 观察矩阵 世界空间->观察空间
    QMatrix4x4 view;
    view.translate(cameraPos);
    //view.lookAt(cameraPos, cameraTarget, cameraUp);
    mShader->setUniformValue("view", view);
    qDebug() << "view = " << view;

    // 投影矩阵 观察空间->裁剪空间 重要作用:将(x,y)->(-1,1)
    QMatrix4x4 projection;
    projection.ortho(clipXArea.first, clipXArea.second, clipYArea.first, clipYArea.second, 1, -1);
    mShader->setUniformValue("projection", projection);
    qDebug() << "Projection = " << projection;

    glViewport(0, 0, width(), height()); // 本应该放在resizeGL()中,但是在Qt中测试,在resizeGL中无效,故写在这里

    mVao->bind();

    glPointSize(10.0f);
    glDrawArrays(GL_POINTS, 0, 3);                        // 画点云 pra1-图元类型 pra2-从数组的哪个位置开始绘制 pra3-绘制数量

    mShader->release();
    mVao->release();
}

根据屏幕坐标获取世界坐标的实现函数如下:

QPoint MyGLWidget::getOriginPos(const QPoint & pos)
{
    #pragma region 自己逆乘

    // 原理 屏幕坐标 = port * projection * view * model * 原始坐标 (注意是左乘)

    // 模型矩阵 局部空间->世界空间
    QMatrix4x4 model;
    model.rotate(0, QVector3D(1.0f, 0.0f, 0.0f));

    // 观察矩阵 世界空间->观察空间
    QMatrix4x4 view;
    //view.translate(cameraPos);
    view.lookAt(cameraPos, cameraTarget, cameraUp);
    mShader->setUniformValue("view", view);

    // 投影矩阵 观察空间->投影空间 作用:将(x,y)->(-1,1)
    QMatrix4x4 projection;
    projection.ortho(clipXArea.first, clipXArea.second, clipYArea.first, clipYArea.second, 0, 0);

    // 视口矩阵 投影空间->屏幕空间
    QMatrix4x4 port;
    port.viewport(0, 0, width(), height());
    qDebug() << "port = " << port;
    qDebug() << "width = " << width() << "height = " << height();

    QVector4D posInMat = QVector4D(pos.x(), pos.y(), 0, 1);

    QVector4D retPosInMat = model.inverted() * view.inverted() * projection.inverted() * port.inverted() * posInMat;

    #pragma endregion


    #pragma region 调用glu的glUnproject

    GLdouble modelView[16], projectMat[16];        // 模型矩阵、投影矩阵
    GLint viewPort[4];                            // 视口
    double objx, objy, objz;                    // 获得的世界坐标
    glGetDoublev(GL_PROJECTION_MATRIX, projectMat);
    glGetDoublev(GL_MODELVIEW_MATRIX, modelView);
    glGetIntegerv(GL_VIEWPORT, viewPort);        // 视口
    gluUnProject(pos.x(), pos.y(), 0, modelView, projectMat, viewPort, &objx, &objy, &objz);
    retPosInMat.setX(objx);
    retPosInMat.setY(objy);

    #pragma endregion 
    

    return QPoint(retPosInMat.x(), retPosInMat.y());
}

void MyGLWidget::mousePressEvent(QMouseEvent * event)
{
    QPoint pos = event->pos();
    QPoint retPos = getOriginPos(pos);

    qDebug() << "pos in disply : " << pos << " its origin pos : " << retPos;
}

测试用的顶点数据如下:

vertices = {
        -400.0f, 400.0f, 0.0f,
         400.0f, 400.0f, 0.0f,
        400.0f, -400.0f, 0.0f
    };

附测试过程截屏:

img

img

img

求解结果一直是错误的,自己暂时找不出原因在哪。寻求大家帮助解惑,感谢感谢~

看了你的代码,请检查以下几部分:

  1. OpenGL将标准化设备坐标转到屏幕坐标的时候,是以glViewport传入的前两个参数为视口原点的,并且这个原点是在视口的左下方,向右为X轴正向,向上为Y轴正项(这与Qt拿到的鼠标坐标相反);详细可以参考:https://docs.gl/gl3/glViewport
  2. 你传入的QPoint中的float类型参数,会被向零取整,导致一定误差,可以用QPointF代替;
    为了确定具体错误位置,可以逐个检查每一步变换的矩阵,为了方便计算,其他矩阵设为单位阵。

从你的代码来看 感觉问题出在获取世界坐标时的矩阵运算顺序上

你的getOriginPos函数中,矩阵乘法顺序是反的:
model.inverted() * view.inverted() * projection.inverted()

更正矩阵运算顺序:
世界坐标 = 逆投影矩阵 x 逆视图矩阵 x 逆模型矩阵 x 屏幕坐标

【相关推荐】




如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^

根据你提供的代码和描述,可能出现问题的地方包括以下几个方面:

  1. 矩阵运算的顺序和方向: 在计算变换矩阵时,矩阵的乘法顺序和乘法的方向非常重要。在 OpenGL 中,变换矩阵是从右往左乘的,也就是说,变换是从局部空间到世界空间,再到观察空间,最后到裁剪空间。你在获取世界坐标的函数 getOriginPos 中逆乘的顺序应该是:port.inverted() * projection.inverted() * view.inverted() * model.inverted()

  2. 正交投影矩阵设置: 在投影矩阵的 ortho 方法中,你的 zNearzFar 参数都设置为 0,这可能会导致投影矩阵设置有问题。zNearzFar 参数应该设置为一个合理的范围,比如 -11

  3. 屏幕坐标和窗口大小: 在获取屏幕坐标后,应该根据窗口的实际大小进行调整,以得到正确的世界坐标。

  4. 投影矩阵设置: 确保你的投影矩阵设置正确,将场景映射到裁剪空间。

  5. 顶点坐标的范围: 确保你的顶点坐标范围在合理的范围内,不要超过裁剪空间的范围。

请检查以上几个方面,逐步排除问题。如果问题仍然存在,你可以通过打印输出或调试工具来查看矩阵运算的结果,以便更好地定位问题所在。同时,确保你的顶点数据在屏幕范围内,且在正确的坐标空间内。


OpenGL with QtWidgets:屏幕坐标转世界坐标_opengl 屏幕坐标转世界坐标_龚建波的博客-CSDN博客 (本文是LearnOpenGL的学习笔记,教程中文翻译地址https://learnopengl-cn.github.io/(备用地址https://learnopengl-cn.readthedocs.io/zh/latest/),写于 2022-04-04)0.前言在之前的图形绘制操作中,都是将模型顶点坐标转换为屏幕坐标进行显示。但在进行如人机交互时,可能需要将屏幕坐标转换到世界坐标,比如点击物体进行选中,如果采用射线拾取法,就需要将屏幕坐标系的点转到世界坐标系去。1.知识点先复习下顶._opengl 屏幕坐标转世界坐标 https://blog.csdn.net/gongjianbo1992/article/details/123962918