将鼠标坐标转换为 OpenGL 时出现错误偏移
Converting mouse coordinates to OpenGL is erroneously offset
我正在尝试使用鼠标实现对球体表面顶点的拾取。我正在使用 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 值有问题,我可以看到当您远离中心轴时误差会变大。但我似乎无法弄清楚代码中确切的问题所在。
新错误
现在,点出现在远离球体表面的地方。
在 Perspective Projection 处,投影矩阵描述了从针孔相机看到的世界中的 3D 点到视口的 2D 点的映射。
观看体积是一个Frustum(截角金字塔),其中金字塔的顶部是观看者的位置。
如果从相机位置开始一条射线,则射线上的所有点都具有相同的 xy window 坐标(和 xy 归一化设备坐标),这些点只是具有不同的 "depth"(z 坐标) . view ray投射到viewport上就是一个点。
这说明你的假设是错误的,方向"into the screen"不是(0, 0, -1):
// Create directional vector pointing into the screen.
glm::vec3 into_screen = glm::vec3(0, 0, -1);
请注意,这对于 正交投影 是正确的,但对于 Perspective Projection 是错误的。
方向取决于window坐标。幸运的是,它不依赖于深度,window 坐标由鼠标位置给出。
要找到 "hits" 当前鼠标位置的射线,您必须计算射线上的 2 个点。
找到光线与近平面(深度 = 0.0)和远平面(深度)的交点。由于所有
= 1.0)
这意味着从相机位置开始并通过鼠标光标的射线上 2 个点的 window 坐标是:
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
);
// [...]
}
我正在尝试使用鼠标实现对球体表面顶点的拾取。我正在使用 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 值有问题,我可以看到当您远离中心轴时误差会变大。但我似乎无法弄清楚代码中确切的问题所在。
新错误
现在,点出现在远离球体表面的地方。
在 Perspective Projection 处,投影矩阵描述了从针孔相机看到的世界中的 3D 点到视口的 2D 点的映射。
观看体积是一个Frustum(截角金字塔),其中金字塔的顶部是观看者的位置。
如果从相机位置开始一条射线,则射线上的所有点都具有相同的 xy window 坐标(和 xy 归一化设备坐标),这些点只是具有不同的 "depth"(z 坐标) . view ray投射到viewport上就是一个点。
这说明你的假设是错误的,方向"into the screen"不是(0, 0, -1):
// Create directional vector pointing into the screen. glm::vec3 into_screen = glm::vec3(0, 0, -1);
请注意,这对于 正交投影 是正确的,但对于 Perspective Projection 是错误的。
方向取决于window坐标。幸运的是,它不依赖于深度,window 坐标由鼠标位置给出。
要找到 "hits" 当前鼠标位置的射线,您必须计算射线上的 2 个点。
找到光线与近平面(深度 = 0.0)和远平面(深度)的交点。由于所有
= 1.0)
这意味着从相机位置开始并通过鼠标光标的射线上 2 个点的 window 坐标是:
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
);
// [...]
}