Screen to World Projection (CGMath, Rust)
Recently realized that cgmath does not provide function for projecting between screen space and world space. Similarly to GLM's glm::unProject
and glm::project
in C++, or further back GLU's gluUnProject
and gluProject
.
This post contains an implementation of those functions for cgmath in Rust. The snippets are tested against the following version of the cgmath crate.
cgmath = "0.17.0"
Screen Space to World Space Projection
Same as glm::unProject
.
pub fn project_screen_to_world(
screen: Vector3<f32>,
view_projection: Matrix4<f32>,
viewport: Vector4<i32>,
) -> Option<Vector3<f32>> {
if let Some(inv_view_projection) = view_projection.invert() {
let world = Vector4::new(
(screen.x - (viewport.x as f32)) / (viewport.z as f32) * 2.0 - 1.0,
// Screen Origin is Top Left (Mouse Origin is Top Left)
// (screen.y - (viewport.y as f32)) / (viewport.w as f32) * 2.0 - 1.0,
// Screen Origin is Bottom Left (Mouse Origin is Top Left)
(1.0 - (screen.y - (viewport.y as f32)) / (viewport.w as f32)) * 2.0 - 1.0, screen.z * 2.0 - 1.0,
1.0);
let world = inv_view_projection * world;
if world.w != 0.0 {
Some(world.truncate() * (1.0 / world.w))
} else {
None
}
} else {
None
}
}
World Space to Screen Space Projection
Same as glm::project
.
pub fn project_world_to_screen(
world: Vector3<f32>,
view_projection: Matrix4<f32>,
viewport: Vector4<i32>,
) -> Option<Vector3<f32>> {
let screen = view_projection * world.extend(1.0);
if screen.w != 0.0 {
let mut screen = screen.truncate() * (1.0 / screen.w);
screen.x = (screen.x + 1.0) * 0.5 * (viewport.z as f32) + (viewport.x as f32);
// Screen Origin is Top Left (Mouse Origin is Top Left)
// screen.y = (screen.y + 1.0) * 0.5 * (viewport.w as f32) + (viewport.y as f32);
// Screen Origin is Bottom Left (Mouse Origin is Top Left)
screen.y = (1.0 - screen.y) * 0.5 * (viewport.w as f32) + (viewport.y as f32);
// This is only correct when glDepthRangef(0.0f, 1.0f)
screen.z = (screen.z + 1.0) * 0.5;
Some(screen)
} else {
None
}
}
Example - Screen to World Ray
Given a point on the screen, such as the mouse position (mx
, my
), the view projection matrix (vp
), and viewport
, the following example calculates a ray from the screen and into the world.
struct Ray {
start: Point3<f32>,
direction: Vector3<f32>,
}
let front = project_screen_to_world(Vector3::new(mx, my, 1.0), vp, viewport);
let back = project_screen_to_world(Vector3::new(mx, my, 0.0), vp, viewport);
if let (Some(front), Some(back)) = (front, back) {
let ray = Ray {
start: Point3::from_vec(back),
direction: (front - back).normalize(),
};
}
The inverse of project_screen_to_world
is project_world_to_screen
, and as such can be used to calculate a 2D screen point from a 3D world point.