我正在尝试使用鼠标在球体表面拾取顶点。我正在使用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();
}
// 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值有问题,我可以看到,当您偏离中心轴时,误差会变大。但我似乎无法找出确切的问题在代码中的什么位置。
在“透视投影”中,投影矩阵描述了从针孔相机看到的世界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
);
// [...]
}