Skip to content

Commit

Permalink
Add depth_ndc_to_view_z for cpu-side (bevyengine#14590)
Browse files Browse the repository at this point in the history
# Objective

I want to get the visual depth (after view proj matrix stuff) of the
object beneath my cursor.
Even when having a write-back of the depth texture, you would still need
to convert the NDC depth to a logical value.

## Solution

This is done on shader-side by [this
function](https://github.com/bevyengine/bevy/blob/e6261b0f5f1124ffa67b8fe9a2d24a2047795192/crates/bevy_pbr/src/render/view_transformations.wgsl#L151),
which I ported over to the cpu-side.

I also added `world_to_viewport_with_depth` to get a `Vec3` instead of
`Vec2`.

---

If anyone knows a smarter solution to get the visual depth instead of
going `screen -> viewport ray -> screen`, please let me know :>
  • Loading branch information
DasLixou authored Aug 2, 2024
1 parent 5b29402 commit 7c80ae7
Showing 1 changed file with 51 additions and 0 deletions.
51 changes: 51 additions & 0 deletions crates/bevy_render/src/camera/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,39 @@ impl Camera {
Some(viewport_position)
}

/// Given a position in world space, use the camera to compute the viewport-space coordinates and depth.
///
/// To get the coordinates in Normalized Device Coordinates, you should use
/// [`world_to_ndc`](Self::world_to_ndc).
///
/// Returns `None` if any of these conditions occur:
/// - The computed coordinates are beyond the near or far plane
/// - The logical viewport size cannot be computed. See [`logical_viewport_size`](Camera::logical_viewport_size)
/// - The world coordinates cannot be mapped to the Normalized Device Coordinates. See [`world_to_ndc`](Camera::world_to_ndc)
/// May also panic if `glam_assert` is enabled. See [`world_to_ndc`](Camera::world_to_ndc).
#[doc(alias = "world_to_screen_with_depth")]
pub fn world_to_viewport_with_depth(
&self,
camera_transform: &GlobalTransform,
world_position: Vec3,
) -> Option<Vec3> {
let target_size = self.logical_viewport_size()?;
let ndc_space_coords = self.world_to_ndc(camera_transform, world_position)?;
// NDC z-values outside of 0 < z < 1 are outside the (implicit) camera frustum and are thus not in viewport-space
if ndc_space_coords.z < 0.0 || ndc_space_coords.z > 1.0 {
return None;
}

// Stretching ndc depth to value via near plane and negating result to be in positive room again.
let depth = -self.depth_ndc_to_view_z(ndc_space_coords.z);

// Once in NDC space, we can discard the z element and rescale x/y to fit the screen
let mut viewport_position = (ndc_space_coords.truncate() + Vec2::ONE) / 2.0 * target_size;
// Flip the Y co-ordinate origin from the bottom to the top.
viewport_position.y = target_size.y - viewport_position.y;
Some(viewport_position.extend(depth))
}

/// Returns a ray originating from the camera, that passes through everything beyond `viewport_position`.
///
/// The resulting ray starts on the near plane of the camera.
Expand Down Expand Up @@ -478,6 +511,24 @@ impl Camera {

(!world_space_coords.is_nan()).then_some(world_space_coords)
}

/// Converts the depth in Normalized Device Coordinates
/// to linear view z for perspective projections.
///
/// Note: Depth values in front of the camera will be negative as -z is forward
pub fn depth_ndc_to_view_z(&self, ndc_depth: f32) -> f32 {
let near = self.clip_from_view().w_axis.z; // [3][2]
-near / ndc_depth
}

/// Converts the depth in Normalized Device Coordinates
/// to linear view z for orthographic projections.
///
/// Note: Depth values in front of the camera will be negative as -z is forward
pub fn depth_ndc_to_view_z_2d(&self, ndc_depth: f32) -> f32 {
-(self.clip_from_view().w_axis.z - ndc_depth) / self.clip_from_view().z_axis.z
// [3][2] [2][2]
}
}

/// Control how this camera outputs once rendering is completed.
Expand Down

0 comments on commit 7c80ae7

Please sign in to comment.