OpenGL C++ 鼠标射线拾取 glm:unproject

5

我目前正在开发一款C++游戏引擎,并希望在应用程序中构建鼠标交互。我以前使用射线拾取来实现过这个功能,但当时我使用的是固定的鼠标位置,现在我想不用这种方法来实现。我看到可以使用glm::unProject函数来实现这个目的,但我的方案却不起作用。该函数给出的坐标不正确。我做错了什么?

rscore_projection_matrix = glm::perspective(45.0f, (float)(windowWidth)/(float)(windowHeight), 0.1f, 1000.0f);
rscore_view_matrix = glm::lookAt(glm::vec3(lengthdir_x(16, rscam_direction)+rscam_x, rscam_z, lengthdir_y(16, rscam_direction)+rscam_y), glm::vec3(rscam_x, 0, rscam_y), glm::vec3(0,1,0));
rscore_model_matrix = glm::mat4(1.0f);
glm::vec3 screenPos = glm::vec3(rscore_mouse_x, rscore_mouse_y, 0.1f);
glm::vec4 viewport = glm::vec4(0.0f, 0.0f, windowWidth, windowHeight);
glm::vec3 worldPos = glm::unProject(screenPos, rscore_model_matrix, rscore_projection_matrix, viewport);

我使用vec3 worldPos 位置来绘制对象。

“just doesn't work” 是什么意思?你应该提供更多的源代码。你正在使用渲染的矩阵是相同的吗?screenPos 实际上是否使用了正确的OpenGL约定(视口左下角为原点)? - derhass
@derhass 是的,上面的三个变量也是我用来渲染场景的变量。我也尝试在screenPos代码中镜像垂直鼠标,但这也不能正常工作。我画了一个立方体,应该代表鼠标指针在3D空间中的位置,但这个立方体只是在房间里的某个地方(我认为是在原点),并且在我移动鼠标指针时只移动了一点点。我也不明白为什么视图矩阵没有参与到unProject脚本中。 - The RuneSnake
1
视图矩阵必须参与其中。GLM的“model”参数名称在这里可能有点误导。您必须将_modelView_矩阵放在那里。 - derhass
3个回答

16

我不确定这是否对你有帮助,但是我是这样实现光线拾取(计算光线的方向)的:

glm::vec3 CFreeCamera::CreateRay() {
    // these positions must be in range [-1, 1] (!!!), not [0, width] and [0, height]
    float mouseX = getMousePositionX() / (getWindowWidth()  * 0.5f) - 1.0f;
    float mouseY = getMousePositionY() / (getWindowHeight() * 0.5f) - 1.0f;

    glm::mat4 proj = glm::perspective(FoV, AspectRatio, Near, Far);
    glm::mat4 view = glm::lookAt(glm::vec3(0.0f), CameraDirection, CameraUpVector);

    glm::mat4 invVP = glm::inverse(proj * view);
    glm::vec4 screenPos = glm::vec4(mouseX, -mouseY, 1.0f, 1.0f);
    glm::vec4 worldPos = invVP * screenPos;

    glm::vec3 dir = glm::normalize(glm::vec3(worldPos));

    return dir;
}

// Values you might be interested:
glm::vec3 cameraPosition; // some camera position, this is supplied by you
glm::vec3 rayDirection = CFreeCamera::CreateRay();
glm::vec3 rayStartPositon = cameraPosition;
glm::vec3 rayEndPosition = rayStartPosition + rayDirection * someDistance;

解释:

当你将顶点的位置乘以视图和投影矩阵时,你会得到像素的位置。如果你将像素位置乘以视图和投影矩阵的逆矩阵,那么你会得到世界的位置。

虽然计算逆矩阵是昂贵的,但我不确定 glm::unProject 的工作方式是否相同。

这只给出了光线的世界定向方向(并且你应该已经有相机的位置)。这段代码不处理与物体的“碰撞”。

相机类的其他代码在这里

更多信息可以在示例中找到


我已经将代码改成了你的,但是如何计算射线起点和终点的方向?我试过一点点,但是射线现在总是在相机后面。`float tcam_x, tcam_y, tcam_z; tcam_x = lengthdir_x(16, rscam_direction)+rscam_x; tcam_z = rscam_z; tcam_y = lengthdir_y(16, rscam_direction)+rscam_y;glm::vec3 dir = glm::normalize(glm::vec3(worldPos)); cx = dir[0]*16+tcam_x; cy = dir[2]*16+tcam_y; cz = dir[1]*16+tcam_z;` - The RuneSnake
我不确定问题出在哪里,而且你的代码没有意义(同时,把它发布在类似pastebin这样的网站上)。在这里可以找到关于这种技术的详细解释,或者在stackoverflow相关网站的这里这里 中查看更简短的版本。基本上起点是相机的位置(所以不能在后面!),终点是起点+方向*某个(最大)距离。 - RippeR
另外,我之前没有写,但是你的鼠标坐标必须缩放到[-1, 1](设备坐标),而不是[0,width]和[0,height]。答案已更新。 - RippeR
谢谢,将鼠标缩放为-1,1后,它终于可以工作了! - The RuneSnake
通过执行(视图*投影)而无需反演,我的射线指向正确的方向,但有时会错过我单击的实际坐标。 有时它会有点偏左、偏右等。 - IOviSpot
显示剩余2条评论

0

glm 实现这些函数(文档):

template<typename T, typename U, qualifier Q>
GLM_FUNC_QUALIFIER vec<3, T, Q> unProjectZO(vec<3, T, Q> const& win, mat<4, 4, T, Q> const& model, mat<4, 4, T, Q> const& proj, vec<4, U, Q> const& viewport)
{
    mat<4, 4, T, Q> Inverse = inverse(proj * model);

    vec<4, T, Q> tmp = vec<4, T, Q>(win, T(1));
    tmp.x = (tmp.x - T(viewport[0])) / T(viewport[2]);
    tmp.y = (tmp.y - T(viewport[1])) / T(viewport[3]);
    tmp.x = tmp.x * static_cast<T>(2) - static_cast<T>(1);
    tmp.y = tmp.y * static_cast<T>(2) - static_cast<T>(1);

    vec<4, T, Q> obj = Inverse * tmp;
    obj /= obj.w;

    return vec<3, T, Q>(obj);
}

template<typename T, typename U, qualifier Q>
GLM_FUNC_QUALIFIER vec<3, T, Q> unProjectNO(vec<3, T, Q> const& win, mat<4, 4, T, Q> const& model, mat<4, 4, T, Q> const& proj, vec<4, U, Q> const& viewport)
{
    mat<4, 4, T, Q> Inverse = inverse(proj * model);

    vec<4, T, Q> tmp = vec<4, T, Q>(win, T(1));
    tmp.x = (tmp.x - T(viewport[0])) / T(viewport[2]);
    tmp.y = (tmp.y - T(viewport[1])) / T(viewport[3]);
    tmp = tmp * static_cast<T>(2) - static_cast<T>(1);

    vec<4, T, Q> obj = Inverse * tmp;
    obj /= obj.w;

    return vec<3, T, Q>(obj);
}

0

下面你可以看到gluUnproject的工作原理。这突显了你忘记使用视图矩阵,而只使用了模型矩阵。

int glhUnProjectf(float winx, float winy, float winz,
    float* modelview, float* projection, int* viewport, float* objectCoordinate)
{
    // Transformation matrices
    float m[16], A[16];
    float in[4], out[4];
    // Calculation for inverting a matrix, compute projection x modelview
    // and store in A[16]
    MultiplyMatrices4by4OpenGL_FLOAT(A, projection, modelview);
    // Now compute the inverse of matrix A
    if(glhInvertMatrixf2(A, m)==0)
       return 0;
    // Transformation of normalized coordinates between -1 and 1
    in[0]=(winx-(float) viewport[0])/(float) viewport[2]*2.0-1.0;
    in[1]=(winy-(float) viewport[1])/(float) viewport[3]*2.0-1.0;
    in[2]=2.0* winz-1.0;
    in[3]=1.0;
    // Objects coordinates
    MultiplyMatrixByVector4by4OpenGL_FLOAT(out, m, in);
    if(out[3]==0.0)
       return 0;
    out[3]=1.0/out[3];
    objectCoordinate[0]=out[0]*out[3];
    objectCoordinate[1]=out[1]*out[3];
    objectCoordinate[2]=out[2]*out[3];
    return 1;
}

这段代码来自这里


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接