我发现的大多数答案都涉及将
vector of normalised device coordinates
乘以 inverse(projection * view)
矩阵,但是我尝试过的每个示例都会导致至少两个无效的结果..
worldray.xy
在不同的ndc.z
范围内没有变化,阻止我在不同的近/远平面上生成方向矢量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
得到的数字
这是我用来生成从屏幕空间到场景的标准化光线的函数:
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:
glReadPixels
获取鼠标/纹理坐标处的深度值,您可以将其从 NDC 转换回世界空间。额外: 4. 如果您正在进行对象拾取,您可以通过使用缓冲区来标记场景中的每个不同对象并使用
glReadPixels
从其唯一的颜色标签来 ID 对象,从而获得像素完美的 GPU 鼠标拾取。
我通常将选项 1. 用于 3D 数学工作流程,并发现它足以满足对象拾取、拖动、绘制 3D 线条等操作...
第一个答案几乎是正确的,但它正在动态切换前进方向。这可能就是向量具有如此小的值的原因。因为代码将 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));
}