Skip to content

Commit 7c80ae7

Browse files
authored
Add depth_ndc_to_view_z for cpu-side (#14590)
# 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 :>
1 parent 5b29402 commit 7c80ae7

File tree

1 file changed

+51
-0
lines changed

1 file changed

+51
-0
lines changed

crates/bevy_render/src/camera/camera.rs

+51
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,39 @@ impl Camera {
373373
Some(viewport_position)
374374
}
375375

376+
/// Given a position in world space, use the camera to compute the viewport-space coordinates and depth.
377+
///
378+
/// To get the coordinates in Normalized Device Coordinates, you should use
379+
/// [`world_to_ndc`](Self::world_to_ndc).
380+
///
381+
/// Returns `None` if any of these conditions occur:
382+
/// - The computed coordinates are beyond the near or far plane
383+
/// - The logical viewport size cannot be computed. See [`logical_viewport_size`](Camera::logical_viewport_size)
384+
/// - The world coordinates cannot be mapped to the Normalized Device Coordinates. See [`world_to_ndc`](Camera::world_to_ndc)
385+
/// May also panic if `glam_assert` is enabled. See [`world_to_ndc`](Camera::world_to_ndc).
386+
#[doc(alias = "world_to_screen_with_depth")]
387+
pub fn world_to_viewport_with_depth(
388+
&self,
389+
camera_transform: &GlobalTransform,
390+
world_position: Vec3,
391+
) -> Option<Vec3> {
392+
let target_size = self.logical_viewport_size()?;
393+
let ndc_space_coords = self.world_to_ndc(camera_transform, world_position)?;
394+
// NDC z-values outside of 0 < z < 1 are outside the (implicit) camera frustum and are thus not in viewport-space
395+
if ndc_space_coords.z < 0.0 || ndc_space_coords.z > 1.0 {
396+
return None;
397+
}
398+
399+
// Stretching ndc depth to value via near plane and negating result to be in positive room again.
400+
let depth = -self.depth_ndc_to_view_z(ndc_space_coords.z);
401+
402+
// Once in NDC space, we can discard the z element and rescale x/y to fit the screen
403+
let mut viewport_position = (ndc_space_coords.truncate() + Vec2::ONE) / 2.0 * target_size;
404+
// Flip the Y co-ordinate origin from the bottom to the top.
405+
viewport_position.y = target_size.y - viewport_position.y;
406+
Some(viewport_position.extend(depth))
407+
}
408+
376409
/// Returns a ray originating from the camera, that passes through everything beyond `viewport_position`.
377410
///
378411
/// The resulting ray starts on the near plane of the camera.
@@ -478,6 +511,24 @@ impl Camera {
478511

479512
(!world_space_coords.is_nan()).then_some(world_space_coords)
480513
}
514+
515+
/// Converts the depth in Normalized Device Coordinates
516+
/// to linear view z for perspective projections.
517+
///
518+
/// Note: Depth values in front of the camera will be negative as -z is forward
519+
pub fn depth_ndc_to_view_z(&self, ndc_depth: f32) -> f32 {
520+
let near = self.clip_from_view().w_axis.z; // [3][2]
521+
-near / ndc_depth
522+
}
523+
524+
/// Converts the depth in Normalized Device Coordinates
525+
/// to linear view z for orthographic projections.
526+
///
527+
/// Note: Depth values in front of the camera will be negative as -z is forward
528+
pub fn depth_ndc_to_view_z_2d(&self, ndc_depth: f32) -> f32 {
529+
-(self.clip_from_view().w_axis.z - ndc_depth) / self.clip_from_view().z_axis.z
530+
// [3][2] [2][2]
531+
}
481532
}
482533

483534
/// Control how this camera outputs once rendering is completed.

0 commit comments

Comments
 (0)