From 7c80ae73133f9866f2ed477ce9922c83fc3fe1cc Mon Sep 17 00:00:00 2001 From: Lixou <82600264+DasLixou@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:37:29 +0200 Subject: [PATCH] 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 :> --- crates/bevy_render/src/camera/camera.rs | 51 +++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 4e4852db43..4f533345b9 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -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 { + 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. @@ -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.