diff --git a/crates/viewer/re_renderer/shader/depth_cloud.wgsl b/crates/viewer/re_renderer/shader/depth_cloud.wgsl index b2784acc7bb4..1504e2579aed 100644 --- a/crates/viewer/re_renderer/shader/depth_cloud.wgsl +++ b/crates/viewer/re_renderer/shader/depth_cloud.wgsl @@ -182,9 +182,14 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut { return out; } +fn coverage(world_position: vec3f, radius: f32, point_center: vec3f) -> f32 { + let sphere_intersection = ray_sphere_distance(camera_ray_to_world_pos(world_position), point_center, radius); + return sphere_quad_coverage(sphere_intersection); +} + @fragment fn fs_main(in: VertexOut) -> @location(0) vec4f { - let coverage = sphere_quad_coverage(in.pos_in_world, in.point_radius, in.point_pos_in_world); + let coverage = coverage(in.pos_in_world, in.point_radius, in.point_pos_in_world); if coverage < 0.001 { discard; } @@ -193,7 +198,7 @@ fn fs_main(in: VertexOut) -> @location(0) vec4f { @fragment fn fs_main_picking_layer(in: VertexOut) -> @location(0) vec4u { - let coverage = sphere_quad_coverage(in.pos_in_world, in.point_radius, in.point_pos_in_world); + let coverage = coverage(in.pos_in_world, in.point_radius, in.point_pos_in_world); if coverage <= 0.5 { discard; } @@ -205,7 +210,7 @@ fn fs_main_outline_mask(in: VertexOut) -> @location(0) vec2u { // Output is an integer target, can't use coverage therefore. // But we still want to discard fragments where coverage is low. // Since the outline extends a bit, a very low cut off tends to look better. - let coverage = sphere_quad_coverage(in.pos_in_world, in.point_radius, in.point_pos_in_world); + let coverage = coverage(in.pos_in_world, in.point_radius, in.point_pos_in_world); if coverage < 1.0 { discard; } diff --git a/crates/viewer/re_renderer/shader/point_cloud.wgsl b/crates/viewer/re_renderer/shader/point_cloud.wgsl index 1d9c441b6468..3cd8f247627e 100644 --- a/crates/viewer/re_renderer/shader/point_cloud.wgsl +++ b/crates/viewer/re_renderer/shader/point_cloud.wgsl @@ -66,6 +66,14 @@ struct VertexOut { picking_instance_id: vec2u, }; +struct FragmentOut { + @location(0) + color: vec4f, + + @builtin(frag_depth) + depth: f32 +} + struct PointData { pos: vec3f, unresolved_radius: f32, @@ -136,49 +144,78 @@ fn circle_quad_coverage(world_position: vec3f, radius: f32, circle_center: vec3f return smoothstep(radius + feathering_radius, radius - feathering_radius, distance); } -fn coverage(world_position: vec3f, radius: f32, point_center: vec3f) -> f32 { +fn coverage_and_depth(projected_z: f32, world_position: vec3f, radius: f32, point_center: vec3f) -> vec2f { if is_camera_orthographic() || has_any_flag(batch.flags, FLAG_DRAW_AS_CIRCLES) { - return circle_quad_coverage(world_position, radius, point_center); + let coverage = circle_quad_coverage(world_position, radius, point_center); + let depth = projected_z; + return vec2f(coverage, depth); } else { - return sphere_quad_coverage(world_position, radius, point_center); + let camera_ray = camera_ray_to_world_pos(world_position); + let sphere_intersection = ray_sphere_distance(camera_ray_to_world_pos(world_position), point_center, radius); + let coverage = sphere_quad_coverage(sphere_intersection); + let sphere_hit_position = camera_ray.origin + camera_ray.direction * sphere_intersection.distance_to_closest_hit_on_ray; + let sphere_hit_position_projected = apply_depth_offset(frame.projection_from_world * vec4f(sphere_hit_position, 1.0), batch.depth_offset); + let depth = sphere_hit_position_projected.z / sphere_hit_position_projected.w; + return vec2f(coverage, depth); } } @fragment -fn fs_main(in: VertexOut) -> @location(0) vec4f { - let coverage = coverage(in.world_position, in.radius, in.point_center); - if coverage < 0.001 { +fn fs_main(in: VertexOut) -> FragmentOut { + let coverage_depth = coverage_and_depth(in.position.z, in.world_position, in.radius, in.point_center); + if coverage_depth.x < 0.001 { discard; } - // TODO(andreas): Do we want manipulate the depth buffer depth to actually render spheres? // TODO(andreas): Proper shading // TODO(andreas): This doesn't even use the sphere's world position for shading, the world position used here is flat! var shading = 1.0; if has_any_flag(batch.flags, FLAG_ENABLE_SHADING) { shading = max(0.4, sqrt(1.2 - distance(in.point_center, in.world_position) / in.radius)); // quick and dirty coloring } - return vec4f(in.color.rgb * shading, coverage); + return FragmentOut( + vec4f(in.color.rgb * shading, coverage_depth.x), + coverage_depth.y, + ); +} + +struct PickingLayerOut { + @location(0) + picking_id: vec4u, + + @builtin(frag_depth) + depth: f32 } @fragment -fn fs_main_picking_layer(in: VertexOut) -> @location(0) vec4u { - let coverage = coverage(in.world_position, in.radius, in.point_center); - if coverage <= 0.5 { +fn fs_main_picking_layer(in: VertexOut) -> PickingLayerOut { + let coverage_depth = coverage_and_depth(in.position.z, in.world_position, in.radius, in.point_center); + if coverage_depth.x <= 0.5 { discard; } - return vec4u(batch.picking_layer_object_id, in.picking_instance_id); + return PickingLayerOut( + vec4u(batch.picking_layer_object_id, in.picking_instance_id), + coverage_depth.y, + ); +} + +struct OutlineMaskOut { + @location(0) + mask: vec2u, + + @builtin(frag_depth) + depth: f32 } @fragment -fn fs_main_outline_mask(in: VertexOut) -> @location(0) vec2u { +fn fs_main_outline_mask(in: VertexOut) -> OutlineMaskOut { // Output is an integer target, can't use coverage therefore. // But we still want to discard fragments where coverage is low. // Since the outline extends a bit, a very low cut off tends to look better. - let coverage = coverage(in.world_position, in.radius, in.point_center); - if coverage < 1.0 { + let coverage_depth = coverage_and_depth(in.position.z, in.world_position, in.radius, in.point_center); + if coverage_depth.x < 1.0 { discard; } - return batch.outline_mask; + return OutlineMaskOut(batch.outline_mask, coverage_depth.y); } diff --git a/crates/viewer/re_renderer/shader/utils/camera.wgsl b/crates/viewer/re_renderer/shader/utils/camera.wgsl index 3a30bbfdbae0..c8f1b327bcd4 100644 --- a/crates/viewer/re_renderer/shader/utils/camera.wgsl +++ b/crates/viewer/re_renderer/shader/utils/camera.wgsl @@ -63,16 +63,23 @@ fn camera_ray_direction_from_screenuv(texcoord: vec2f) -> vec3f { return normalize(world_space_dir); } -// Returns distance to sphere surface (x) and distance to closest ray hit (y) +struct SphereIntersection { + distance_to_sphere_surface: f32, + distance_to_closest_hit_on_ray: f32, +} + +// Returns distance to sphere surface and distance to closest ray hit. // Via https://iquilezles.org/articles/spherefunctions/ but with more verbose names. -fn ray_sphere_distance(ray: Ray, sphere_origin: vec3f, sphere_radius: f32) -> vec2f { +fn ray_sphere_distance(ray: Ray, sphere_origin: vec3f, sphere_radius: f32) -> SphereIntersection { let sphere_radius_sq = sphere_radius * sphere_radius; let sphere_to_origin = ray.origin - sphere_origin; let b = dot(sphere_to_origin, ray.direction); let c = dot(sphere_to_origin, sphere_to_origin) - sphere_radius_sq; let h = b * b - c; - let d = sqrt(max(0.0, sphere_radius_sq - h)) - sphere_radius; - return vec2f(d, -b - sqrt(max(h, 0.0))); + var intersection: SphereIntersection; + intersection.distance_to_sphere_surface = sqrt(max(0.0, sphere_radius_sq - h)) - sphere_radius; + intersection.distance_to_closest_hit_on_ray = -b - sqrt(max(h, 0.0)); + return intersection; } // Returns the projected size of a pixel at a given distance from the camera. diff --git a/crates/viewer/re_renderer/shader/utils/sphere_quad.wgsl b/crates/viewer/re_renderer/shader/utils/sphere_quad.wgsl index ad61de568e9e..ecbbbeb005db 100644 --- a/crates/viewer/re_renderer/shader/utils/sphere_quad.wgsl +++ b/crates/viewer/re_renderer/shader/utils/sphere_quad.wgsl @@ -89,16 +89,12 @@ fn sphere_or_circle_quad_span(vertex_idx: u32, point_pos: vec3f, world_radius: f } /// Computes coverage of a 3D sphere placed at `sphere_center` in the fragment shader using the currently set camera. -fn sphere_quad_coverage(world_position: vec3f, radius: f32, sphere_center: vec3f) -> f32 { - let ray = camera_ray_to_world_pos(world_position); - +fn sphere_quad_coverage(sphere_intersection: SphereIntersection) -> f32 { // Sphere intersection with anti-aliasing as described by Iq here // https://www.shadertoy.com/view/MsSSWV // (but rearranged and labeled to it's easier to understand!) - let d = ray_sphere_distance(ray, sphere_center, radius); - let distance_to_sphere_surface = d.x; - let closest_ray_dist = d.y; - let pixel_world_size = approx_pixel_world_size_at(closest_ray_dist); + let distance_to_sphere_surface = sphere_intersection.distance_to_sphere_surface; + let pixel_world_size = approx_pixel_world_size_at(sphere_intersection.distance_to_closest_hit_on_ray); let distance_to_surface_in_pixels = distance_to_sphere_surface / pixel_world_size;