Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render points with per-fragment depth, improving visuals for points intersecting with other geometry #6508

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: 53 additions & 16 deletions crates/viewer/re_renderer/shader/point_cloud.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
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
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
Loading