1

在 Qt 论坛中,我发现了这个问题并激发了我的好奇心。

我在 Qt 中找到了一个非常简单的示例来显示一个立方体,并修改了立方体逻辑以创建一个边长为 1 个单位的立方体。

然后我尝试点击模型并显示我点击的区域的坐标。

当不涉及旋转时,似乎采摘工作正常。但!如果我取消注释旋转(或其中一个),paintGL我最终会得到“错误”的值。

例如:

  • 没有旋转,几乎点击了立方体的最左边的边框:gluUnProject方法说我点击了点{ -0.49075, 0.234, 0.5 },这似乎没问题。

没有旋转的立方体

  • 启用旋转并几乎单击多维数据集的最左侧边框:我收到了 point { -0.501456, 0.157555, -0.482942 },这似乎是错误的。坐标不在其x范围内-0.5, 0.5

启用了两个旋转的立方体

我认为坐标转换是可以的。我一直在通过谷歌进行研究,人们总是使用相同的代码。此外,像素的颜色信息与我点击的面部颜色相匹配。

那么,谁能告诉我为什么当涉及旋转时我得到错误的坐标?我认为我在一些基本的 3D 理解方面失败了,但我无法意识到它在哪里。

这是代码:

主.cpp:

#include <QApplication>
#include "GLCube.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    GLCube w;
    w.resize(800,600);
    w.show();

    return a.exec();
}

GLCube.h

#ifndef GLCUBE_H
#define GLCUBE_H

#include <QtOpenGL>
#include <QGLWidget>

class GLCube : public QGLWidget{
  Q_OBJECT // must include this if you use Qt signals/slots
public:
    GLCube(QWidget *parent = NULL)
        : QGLWidget(parent) {
    }
protected:
    // Set up the rendering context, define display lists etc.:
   void initializeGL();
   // draw the scene:
   void paintGL();
   // setup viewport, projection etc.:
   void resizeGL (int width, int height);
   virtual void mousePressEvent(QMouseEvent *pme);
};

#endif

GLCube.cpp:

#include "GLCube.h"

#include <cmath>
#include <iostream>
#include <iomanip>

#include "GL/glu.h"

namespace {
    float ver[8][3] =
    {
        { 0.5, -0.5, 0.5 },
        { -0.5, -0.5, 0.5 },
        { -0.5, -0.5, -0.5 },
        { 0.5, -0.5, -0.5 },
        { 0.5, 0.5, 0.5 },
        { -0.5, 0.5, 0.5 },
        { -0.5, 0.5, -0.5 },
        { +0.5, 0.5, -0.5 }
    };
    GLfloat color[8][4] =
    {
        {0.0,0.0,0.0, 1.0},
        {1.0,0.0,0.0, 1.0 },
        {1.0,1.0,0.0, 1.0 },
        {0.0,1.0,0.0, 1.0 },
        {0.0,0.0,1.0, 1.0 },
        {1.0,0.0,1.0, 1.0 },
        {1.0,1.0,1.0, 1.0 },
        {0.0,1.0,1.0, 1.0 },
    };

    void quad(int a,int b,int c,int d, int col)
    {
        glPointSize( 5 );
        glBegin(GL_POINTS);
            glColor4fv(color[1]);
            glVertex3fv(ver[a]);

            glColor4fv(color[2]);
            glVertex3fv(ver[b]);

            glColor4fv(color[3]);
            glVertex3fv(ver[c]);

            glColor4fv(color[4]);
            glVertex3fv(ver[d]);
        glEnd();

        glBegin(GL_LINES);
            glColor4fv(color[1]);
            glVertex3fv(ver[a]);
            glVertex3fv(ver[b]);

            glColor4fv(color[1]);
            glVertex3fv(ver[b]);
            glVertex3fv(ver[c]);

            glColor4fv(color[1]);
            glVertex3fv(ver[c]);
            glVertex3fv(ver[d]);

            glColor4fv(color[1]);
            glVertex3fv(ver[d]);
            glVertex3fv(ver[a]);
        glEnd();

        glBegin(GL_QUADS);
        glColor4fv(/*color[a]*/ color[col] );
        glVertex3fv(ver[a]);

    //    glColor3fv(color[b]);
        glVertex3fv(ver[b]);

    //    glColor3fv(color[c]);
        glVertex3fv(ver[c]);

    //    glColor3fv(color[d]);
        glVertex3fv(ver[d]);
        glEnd();
    }

    void colorcube()
    {
//        glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
        quad( 3, 2, 1, 0, 1 ); // bottom
        quad( 7, 6, 5, 4, 2); // top
        quad( 6, 2, 1, 5, 3 ); // front
        quad( 7, 3, 0, 4, 5 ); // back
        quad( 7, 6, 2, 3, 6 ); // left
        quad( 4, 5, 1, 0, 7); // right
    }
}
/*
 * Sets up the OpenGL rendering context, defines display lists, etc.
 * Gets called once before the first time resizeGL() or paintGL() is called.
 */
void GLCube::initializeGL(){
    //activate the depth buffer
    glEnable(GL_DEPTH_TEST);
    qglClearColor(Qt::black);
    glEnable(GL_CULL_FACE);
}


/*
 *  Sets up the OpenGL viewport, projection, etc. Gets called whenever the widget has been resized
 *  (and also when it is shown for the first time because all newly created widgets get a resize event automatically).
 */
void GLCube::resizeGL (int width, int height){
    glViewport( 0, 0, (GLint)width, (GLint)height );
    /* create viewing cone with near and far clipping planes */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum( -1.0, 1.0, -1.0, 1.0, 15.0, 30.0);

    glMatrixMode( GL_MODELVIEW );
}

/*
 * Renders the OpenGL scene. Gets called whenever the widget needs to be updated.
 */
void GLCube::paintGL(){

    //delete color and depth buffer
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(0.0f,0.0f,-20.0f); //move along z-axis
        glRotatef(30.0,0.0,1.0,0.0); //rotate 30 degress around y-axis
        glRotatef(15.0,1.0,0.0,0.0); //rotate 15 degress around x-axis

    colorcube();
//    originalcube();
}

void GLCube::mousePressEvent(QMouseEvent *pme) {
    GLint viewport[4];
    GLdouble modelview[16];
    GLdouble projection[16];
    glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
    glGetDoublev( GL_PROJECTION_MATRIX, projection );
    glGetIntegerv( GL_VIEWPORT, viewport );

    const int x = pme->x();
    const int y = viewport[3] - pme->y();

    qDebug() << "HERE: " << x << y;

    GLfloat color[4];
    glReadPixels( x, y, 1, 1, GL_RGBA, GL_FLOAT, color);
    GLenum error = glGetError();
    std::cout << "RETRIEVED COLOR:" << color[0] << ", " << color[1] << ", " << color[2] << ", " << color[3] << std::endl;
    printf( "\tERROR: %s (Code: %u)\n", gluErrorString(error), error );
    if(GL_NO_ERROR != error) throw;

    GLdouble depthScale;
    glGetDoublev( GL_DEPTH_SCALE, &depthScale );
    std::cout << "DEPTH SCALE: " << depthScale << std::endl;
    GLfloat z;
    glReadPixels( x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z );
    error = glGetError();
    std::cout << "X: " << x << ", Y: " << y  << ", RETRIEVED Z: " << z << std::endl;
    printf( "\tERROR: %s (Code: %u)\n", gluErrorString(error), error );
    if(GL_NO_ERROR != error) throw;
    std::cout << std::endl << std::endl;

    GLdouble posX, posY, posZ;
    GLint result;
    result = gluUnProject( x, y, z, modelview, projection, viewport, &posX, &posY, &posZ);
    error = glGetError();
    std::cout << "3D point with POS: " << posX << " " << posY << " " << posZ << std::endl;
    printf( "\tERROR: %s (Code: %u)\n", gluErrorString(error), error );
    std::cout << "\tglUnProject: " << (( GL_FALSE == result ) ? "FALSE" : "TRUE") << std::endl;
    if(GL_NO_ERROR != error) throw;
}

笔记:

我在 Windows 8.1 64 位、Qt 4.8 和 MinGW 中运行它。

此外,两者都没有错误glReadPixelsgluUnProject退出(错误代码= GL_NO_ERROR

并且glut不可用。只有什么OpenGLQtOpenGL和/或glu提供。

4

1 回答 1

1

这并没有错,您正在读取深度缓冲区以找出用于反向投影的窗口空间 Z 值。

问题是深度缓冲区中可用的精度有限,这会带来一些不准确性。实际上,您不能期望未投影值的范围是完美的 [ -0.5 , 0.5 ]。您将不得不在这里引入一个小 epsilon,因此您的有效范围将类似于 [ -0.5015 , 0.5015 ]。

您可以通过提高深度缓冲区的精度和/或减小近端和远端剪辑平面之间的范围来减轻影响。默认情况下,深度缓冲区通常为 24 位定点,但 32 位定点或浮点深度缓冲区可能会稍微改善您的情况。但是,您永远不会完全消除这个问题。

于 2014-11-07T17:31:01.953 回答