Skip to content

Commit

Permalink
implement accurate depth using existing sphere test and use it in all…
Browse files Browse the repository at this point in the history
… passes of point cloud

doesn't extend to depth point cloud
  • Loading branch information
Wumpf committed Nov 15, 2024
1 parent 86f4eee commit c04da78
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 99 deletions.
11 changes: 8 additions & 3 deletions crates/viewer/re_renderer/shader/depth_cloud.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down
69 changes: 42 additions & 27 deletions crates/viewer/re_renderer/shader/point_cloud.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#import <./utils/flags.wgsl>
#import <./utils/size.wgsl>
#import <./utils/sphere_quad.wgsl>
#import <./utils/sphere_depth.wgsl>
#import <./utils/depth_offset.wgsl>

@group(1) @binding(0)
Expand Down Expand Up @@ -65,10 +64,6 @@ struct VertexOut {

@location(4) @interpolate(flat)
picking_instance_id: vec2u,

// Offset on the projected Z axis corresponding to the frontmost point on the sphere.
@location(5) @interpolate(flat)
sphere_radius_projected_depth: f32,
};

struct FragmentOut {
Expand Down Expand Up @@ -132,7 +127,6 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
out.world_position = quad.pos_in_world;
out.point_center = point_data.pos;
out.picking_instance_id = point_data.picking_instance_id;
out.sphere_radius_projected_depth = sphere_radius_projected_depth(point_data.pos, world_radius);

return out;
}
Expand All @@ -150,57 +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) -> FragmentOut {
let coverage = coverage(in.world_position, in.radius, in.point_center);
if coverage < 0.001 {
let coverage_depth = coverage_and_depth(in.position.z, in.world_position, in.radius, in.point_center);
if coverage_depth.x < 0.001 {
discard;
}

let depth_offset = sphere_fragment_projected_depth(
in.radius,
in.sphere_radius_projected_depth,
in.world_position - in.point_center,
);

// 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 FragmentOut(
vec4f(in.color.rgb * shading, coverage),
in.position.z + depth_offset,
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);
}
15 changes: 11 additions & 4 deletions crates/viewer/re_renderer/shader/utils/camera.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
52 changes: 0 additions & 52 deletions crates/viewer/re_renderer/shader/utils/sphere_depth.wgsl

This file was deleted.

10 changes: 3 additions & 7 deletions crates/viewer/re_renderer/shader/utils/sphere_quad.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
6 changes: 0 additions & 6 deletions crates/viewer/re_renderer/src/workspace_shaders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,6 @@ pub fn init() {
fs.create_file(virtpath, content).unwrap();
}

{
let virtpath = Path::new("shader/utils/sphere_depth.wgsl");
let content = include_str!("../shader/utils/sphere_depth.wgsl").into();
fs.create_file(virtpath, content).unwrap();
}

{
let virtpath = Path::new("shader/utils/sphere_quad.wgsl");
let content = include_str!("../shader/utils/sphere_quad.wgsl").into();
Expand Down

0 comments on commit c04da78

Please sign in to comment.