当涉及旋转时,将鼠标坐标转换为 OpenGL 中的模型坐标
Posted
技术标签:
【中文标题】当涉及旋转时,将鼠标坐标转换为 OpenGL 中的模型坐标【英文标题】:Translating mouse coordinates to model coordinates in OpenGL when rotations are involved 【发布时间】:2014-11-07 13:56:01 【问题描述】:在 Qt 论坛中我找到了this question 并激发了我的好奇心。
我在 Qt 中找到了一个非常简单的示例来显示一个立方体,并修改了立方体逻辑以创建一个边长为 1 个单位的立方体。
然后我尝试点击模型并显示我点击区域的坐标。
当不涉及轮换时,似乎采摘工作正常。但!如果我取消注释 paintGL
中的旋转(或其中之一),我最终会得到“错误”的值。
例如:
没有旋转,几乎点击了立方体的最左边框:gluUnProject
方法说我点击了点 -0.49075, 0.234, 0.5
,这似乎没问题。
启用旋转并几乎单击立方体的最左侧边框:我收到 -0.501456, 0.157555, -0.482942
点,这似乎是错误的。 x
坐标超出了 -0.5, 0.5
的范围。
我觉得坐标变换没问题。我一直在通过谷歌进行研究,人们总是使用相同的代码。而且,像素的颜色信息和我点击的脸的颜色相匹配。
那么,谁能告诉我为什么在涉及旋转时我得到错误的坐标?我认为我在一些基本的 3D 理解方面失败了,但我无法意识到它在哪里。
代码如下:
main.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 中运行它。
另外,glReadPixels
和 gluUnProject
都可以正常退出(错误代码 = GL_NO_ERROR
)
并且glut
不可用。仅提供 OpenGL
、QtOpenGL
和/或 glu
提供的内容。
【问题讨论】:
【参考方案1】:这并没有错,您正在读取深度缓冲区以计算用于反向投影的窗口空间 Z 值。
问题在于深度缓冲区中可用的精度有限,这会带来一些不准确。实际上,您不能期望未投影值的范围是完美的 [-0.5,0.5]。您将不得不在此处引入一个小 epsilon,因此您的有效范围将类似于 [-0.5015,0.5015]。
您可以通过提高深度缓冲区的精度和/或减小近端和远端剪辑平面之间的范围来减轻影响。默认情况下,深度缓冲区通常为 24 位定点,但 32 位定点或浮点深度缓冲区可能会稍微改善您的情况。但是,您永远不会完全消除此问题。
【讨论】:
哦!谢谢!我从没想过不准确。我会试试你的建议! 另外,我注意到当单击模型的边缘时,我得到了合理的值(使用 epsilon,正如您所解释的那样)。但是,如果我在边缘附近点击,但在外面,在黑色的背景中,我会得到类似4.66933, -1.30734, -8.8131
的东西。有没有办法“禁用”它,还是我必须检查返回的点,看看它是否有合理的界限?
@AdriC.S.:发生的情况是您正在读取深度缓冲区的已清除部分。默认清除深度为 1.0。由于您不太可能点击深度正好等于 1.0 的东西(这将是位于远剪切平面上的东西正好),您可以可能只是假设如果您回读 1.0 的深度,则光标下没有任何内容。
我明白了。非常感谢!!以上是关于当涉及旋转时,将鼠标坐标转换为 OpenGL 中的模型坐标的主要内容,如果未能解决你的问题,请参考以下文章