从 2d 鼠标坐标生成 3d 世界射线的正确方法?

问题描述 投票:0回答:2

我发现的大多数答案都涉及将

vector of normalised device coordinates
乘以
inverse(projection * view)
矩阵,但是我尝试过的每个示例都会导致至少两个无效的结果..

  1. worldray.xy
    在不同的
    ndc.z
    范围内没有变化,阻止我在不同的近/远平面上生成方向矢量
  2. 无效
    worldray.z

有人可以从鼠标坐标提供世界射线的工作生成吗?

编辑: 我已经添加了我正在使用的代码,如果我使用

inverse
z
完全偏离了我的预期,至少使用
affineInverse
我得到了准确的
z
为近

mat4 projection = perspective(radians(fov), (Floating)width / (Floating)height, 0.0001f, 10000.f);
vec3 position = { 0, 0, -2 };
vec3 direction = { 0, 0, 1 };
vec3 center = position + direction;
mat4 view = lookAt(position, center, up);
vec2 ndc = {
 -1.0f + 2.0f * mouse.x / width,
  1.0f + -2.0f * mouse.y / height
};
vec4 near = { ndc.x, ndc.y, 0, 1 };
vec4 far = { ndc .x, ndc .y, -1, 1 };
mat4 invP = inverse(projection);
mat4 invV = inverse(view);
vec4 ray_eye_near = invP * near;
ray_eye_near.z = near.z;
vec4 ray_world_near = invV * ray_eye_near;
ray_world_near /= ray_world_near.w;
printf("ray_world_near x: %f, y: %f, z: %f, w: %f\n\r", ray_world_near.x, ray_world_near.y, ray_world_near.z, ray_world_near.w);
vec4 ray_eye_far = invP * far;
ray_eye_far.z = far.z;
vec4 ray_world_far = invV * ray_eye_far;
ray_world_far /= ray_world_far.w;
printf("ray_world_far  x: %f, y: %f, z: %f, w: %f\n\r", ray_world_far.x, ray_world_far.y, ray_world_far.z, ray_world_far.w);

这是我所经历的屏幕截图

编辑2:这些是使用

inverse
而不是
affineInverse
并除以
w
得到的数字

c++ opengl glm-math raycasting mouse-picking
2个回答
1
投票

这是我用来生成从屏幕空间到场景的标准化光线的函数:

vec3 rayCast(double xpos, double ypos, mat4 view, mat4 projection, unsigned SCR_WIDTH, unsigned SCR_HEIGHT) {
    // converts a position from the 2d xpos, ypos to a normalized 3d direction
    float x = (2.0f * xpos) / SCR_WIDTH - 1.0f;
    float y = 1.0f - (2.0f * ypos) / SCR_SHEIGHT; 
    // or (2.0f * ypos) / SCR_HEIGHT - 1.0f; depending on how you calculate ypos/lastY
    float z = 1.0f;
    vec3 ray_nds = vec3(x, y, z);
    vec4 ray_clip = vec4(ray_nds.x, ray_nds.y, -1.0f, 1.0f);
    // eye space to clip we would multiply by projection so
    // clip space to eye space is the inverse projection
    vec4 ray_eye = inverse(projection) * ray_clip;
    // convert point to forwards
    ray_eye = vec4(ray_eye.x, ray_eye.y, -1.0f, 0.0f);
    // world space to eye space is usually multiply by view so
    // eye space to world space is inverse view
    vec4 inv_ray_wor = (inverse(view) * ray_eye);
    vec3 ray_wor = vec3(inv_ray_wor.x, inv_ray_wor.y, inv_ray_wor.z);
    ray_wor = normalize(ray_wor);
    return ray_wor;
}

例如,

// at the last update of the mouse cursor, `lastX, lastY`
vec3 rayMouse = rayCast(lastX, lastY, viewMatrix, projectionMatrix, SCR_WIDTH, SCR_HEIGHT);

这将为您返回一条标准化光线,您可以使用

glm::vec3 worldPos = cameraPos + t * rayMouse
获得沿着该光线进入场景的参数位置,例如,当
t=1
worldPos
沿着鼠标光标进入场景时为 1 个单位时,您可以使用线渲染类来更好地查看正在发生的情况。

注意:

glm::unproject
可用于达到相同的结果:

glm::vec3 worldPos = glm::unproject(glm::vec3(lastX, lastY, 1.0), 
                               viewMatrix, projectionMatrix, 
                               glm::vec4(0,0,SCR_WIDTH, SCR_HEIGHT);
glm::vec3 rayMouse = glm::normalize(worldPos-cameraPos);

注意:这些函数不能用于获取片段在鼠标坐标处的精确世界空间位置,因为您有三个选项 AFAIK:

  1. 您可以使用上面计算的射线与射线-三角形相交函数来检查所有三角形是否相交,或者如果您只是寻找更近似的方法,您可以尝试射线-球体相交或射线-边界框相交。
  2. 使用深度缓冲区重建世界空间位置,无论是在片段着色器中,这在世界空间照明时最常用 如果您有延迟渲染器:
  3. 您可以使用
    glReadPixels
    获取鼠标/纹理坐标处的深度值,您可以将其从 NDC 转换回世界空间。

额外: 4. 如果您正在进行对象拾取,您可以通过使用缓冲区来标记场景中的每个不同对象并使用

glReadPixels
从其唯一的颜色标签来 ID 对象,从而获得像素完美的 GPU 鼠标拾取。

我通常将选项 1. 用于 3D 数学工作流程,并发现它足以满足对象拾取、拖动、绘制 3D 线条等操作...


0
投票

第一个答案几乎是正确的,但它正在动态切换前进方向。这可能就是向量具有如此小的值的原因。因为代码将 ray_eye.z 设置为 -1.0f,所以 x 和 y 值相比之下太小,并且光线比鼠标更指向屏幕中心。

代码应该改成这样:

vec3 screen_pos_to_ray(uint32_t x_pos, uint32_t y_pos, mat4 proj, mat4 view, uint32_t width, uint32_t height)
{
    mat4 inv_proj = inverse(proj);
    // make the z component positive instead of changing it again
    // in the ray_eye output (although whether this value should be
    // -1 or 1 depends on the grapics API; 1.0 for OpenGL & DirectX)
    vec4 ray_clip = vec4(-1.0f, -1.0f, 1.0f, 1.0f);
    vec2 mid_ = vec2(width / 2.0f, height / 2.0f);
    ray_clip.x = ((float)pixel_pos.x - mid_.x) / mid_.x;
    ray_clip.y = -((float)pixel_pos.y - mid_.y) / mid_.y;
    vec4 ray_eye = inv_proj * ray_clip;
    ray_eye.w = 0.0f;
    mat4 inv_view = inverse(view);
    vec4 ray_world = inv_view * ray_eye;
    return normalize(vec3(ray_world));
}
© www.soinside.com 2019 - 2024. All rights reserved.