Skip to content

Line strips are no longer a disconnected series of quads #8065

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

Merged
merged 15 commits into from
Nov 12, 2024
Merged
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
9 changes: 8 additions & 1 deletion crates/viewer/re_renderer/shader/composite.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ struct CompositeUniformBuffer {
outline_color_layer_a: vec4f,
outline_color_layer_b: vec4f,
outline_radius_pixel: f32,
blend_with_background: u32,
};
@group(1) @binding(0)
var<uniform> uniforms: CompositeUniformBuffer;
Expand All @@ -27,11 +28,17 @@ fn main(in: FragmentInput) -> @location(0) vec4f {
// but are about the location of the texel in the target texture.
var color = textureSample(color_texture, nearest_sampler, in.texcoord);


// TODO(andreas): We assume that the color from the texture does *not* have pre-multiplied alpha.
// This is a brittle workaround for the alpha-to-coverage issue described in `ViewBuilder::MAIN_TARGET_ALPHA_TO_COVERAGE_COLOR_STATE`:
// We need this because otherwise the feathered edges of alpha-to-coverage would be overly bright, as after
// MSAA-resolve they end up with an unusually low alpha value relative to the color value.
color = vec4f(color.rgb * color.a, color.a);
if uniforms.blend_with_background == 0 {
// To not apply this hack needlessly and account for alpha from alpha to coverage, we have to ignore alpha values if blending is disabled.
color = vec4f(color.rgb, 1.0);
} else {
color = vec4f(color.rgb * color.a, color.a);
}
Comment on lines +36 to +41
Copy link
Member Author

@Wumpf Wumpf Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

improves alpha to coverage artifact suppression story. In other words this gets non-map views to where they were in 0.19
Tbh. I'm not 100% sure why I did get still artifacts for boxes in 3D without this, but it also didn't feel that surprising.


// Outlines
{
Expand Down
117 changes: 75 additions & 42 deletions crates/viewer/re_renderer/shader/lines.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ const FLAG_CAP_START_EXTEND_OUTWARDS: u32 = 32u;
const FLAG_COLOR_GRADIENT: u32 = 64u;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the really important changes are in this file

const FLAG_FORCE_ORTHO_SPANNING: u32 = 128u;

// Special flags used in the fragment shader.
const FLAG_CAP_TRIANGLE: u32 = FLAG_CAP_START_TRIANGLE | FLAG_CAP_END_TRIANGLE;

// A lot of the attributes don't need to be interpolated across triangles.
// To document that and safe some time we mark them up with @interpolate(flat)
// (see https://www.w3.org/TR/WGSL/#interpolation)
Expand All @@ -60,14 +63,14 @@ struct VertexOut {
@location(1) @interpolate(perspective)
position_world: vec3f,

@location(2) @interpolate(perspective)
center_position: vec3f,
@location(2) @interpolate(flat)
rounded_inner_line_begin: vec3f,

@location(3) @interpolate(flat)
active_radius: f32,
rounded_inner_line_end: vec3f,

@location(4) @interpolate(perspective)
round_cap_circle_center: vec3f,
@location(4) @interpolate(flat)
rounded_inner_line_radius: f32,

@location(5) @interpolate(flat)
fragment_flags: u32,
Expand Down Expand Up @@ -166,6 +169,8 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {

// If the strip indices don't match up for start/end, then we're in a cap triangle!
let is_cap_triangle = pos_data_quad_begin.strip_index != pos_data_quad_end.strip_index;
let is_first_quad_after_cap = !is_cap_triangle && (pos_data_quad_begin.strip_index != pos_data_quad_before.strip_index);
let is_last_quad_before_cap = !is_cap_triangle && (pos_data_quad_end.strip_index != pos_data_quad_after.strip_index);

// Let's determine which one of the two position data is closer to our vertex.
// Which tells us things:
Expand All @@ -189,14 +194,14 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {

// Compute quad_dir & correct center_position for triangle caps.
var quad_dir: vec3f;
var is_at_pointy_end = false;
let is_end_cap_triangle = is_cap_triangle && is_right_triangle && has_any_flag(strip_data.flags, FLAG_CAP_END_TRIANGLE | FLAG_CAP_END_ROUND);
let is_start_cap_triangle = is_cap_triangle && !is_right_triangle && has_any_flag(strip_data.flags, FLAG_CAP_START_TRIANGLE | FLAG_CAP_START_ROUND);
var is_at_pointy_arrow_end = false;
let is_end_cap_triangle = is_cap_triangle && is_right_triangle && has_any_flag(strip_data.flags, FLAG_CAP_END_TRIANGLE);
let is_start_cap_triangle = is_cap_triangle && !is_right_triangle && has_any_flag(strip_data.flags, FLAG_CAP_START_TRIANGLE);
if is_end_cap_triangle {
is_at_pointy_end = is_at_quad_end;
is_at_pointy_arrow_end = is_at_quad_end;
quad_dir = pos_data_quad_begin.pos - pos_data_quad_before.pos; // Go one pos data back.
} else if is_start_cap_triangle {
is_at_pointy_end = !is_at_quad_end;
is_at_pointy_arrow_end = !is_at_quad_end;
quad_dir = pos_data_quad_after.pos - pos_data_quad_end.pos; // Go one pos data forward.
} else if is_cap_triangle {
// Discard vertex.
Expand Down Expand Up @@ -226,19 +231,27 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
triangle_cap_length *= triangle_cap_size_factor;

// Make space for the end cap if this is either the cap itself or the cap follows right after/before this quad.
if !has_any_flag(strip_data.flags, FLAG_CAP_END_EXTEND_OUTWARDS) &&
(is_end_cap_triangle || (is_at_quad_end && pos_data_current.strip_index != pos_data_quad_after.strip_index)) {
var cap_length =
f32(has_any_flag(strip_data.flags, FLAG_CAP_END_ROUND)) * strip_radius +
f32(has_any_flag(strip_data.flags, FLAG_CAP_END_TRIANGLE)) * triangle_cap_length;
center_position -= quad_dir * cap_length;
}
if !has_any_flag(strip_data.flags, FLAG_CAP_START_EXTEND_OUTWARDS) &&
(is_start_cap_triangle || (!is_at_quad_end && pos_data_current.strip_index != pos_data_quad_before.strip_index)) {
var rounded_inner_line_begin = pos_data_quad_begin.pos;
if !has_any_flag(strip_data.flags, FLAG_CAP_START_EXTEND_OUTWARDS) && (is_start_cap_triangle || is_first_quad_after_cap) {
var cap_length =
f32(has_any_flag(strip_data.flags, FLAG_CAP_START_ROUND)) * strip_radius +
f32(has_any_flag(strip_data.flags, FLAG_CAP_START_TRIANGLE)) * triangle_cap_length;
center_position += quad_dir * cap_length;
let offset = quad_dir * cap_length;
rounded_inner_line_begin += offset;
if !is_at_quad_end || is_start_cap_triangle {
center_position += offset;
}
}
var rounded_inner_line_end = pos_data_quad_end.pos;
if !has_any_flag(strip_data.flags, FLAG_CAP_END_EXTEND_OUTWARDS) && (is_end_cap_triangle || is_last_quad_before_cap) {
var cap_length =
f32(has_any_flag(strip_data.flags, FLAG_CAP_END_ROUND)) * strip_radius +
f32(has_any_flag(strip_data.flags, FLAG_CAP_END_TRIANGLE)) * triangle_cap_length;
let offset = quad_dir * cap_length;
rounded_inner_line_end -= offset;
if is_at_quad_end || is_end_cap_triangle {
center_position -= offset;
}
}

// Boost radius only now that we subtracted/added the cap length.
Expand All @@ -253,57 +266,78 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
center_position += quad_dir * (size_boost * select(-1.0, 1.0, is_at_quad_end));
}

var active_radius = strip_radius;
// Filtered list of flags that the fragment shader is interested in.
var fragment_flags = strip_data.flags & FLAG_COLOR_GRADIENT;

// If this is a triangle cap, we blow up our ("virtual") quad by a given factor.
if (is_end_cap_triangle && has_any_flag(strip_data.flags, FLAG_CAP_END_TRIANGLE)) ||
(is_start_cap_triangle && has_any_flag(strip_data.flags, FLAG_CAP_START_TRIANGLE)) {
active_radius *= batch.triangle_cap_width_factor * triangle_cap_size_factor;
strip_radius *= batch.triangle_cap_width_factor * triangle_cap_size_factor;
fragment_flags |= FLAG_CAP_TRIANGLE;
}

// Span up the vertex away from the line's axis, orthogonal to the direction to the camera
let dir_up = normalize(cross(camera_ray.direction, quad_dir));

let round_cap_circle_center = center_position;

var pos: vec3f;
if is_cap_triangle && is_at_pointy_end {
// We extend the cap triangle far enough to handle triangle caps,
// and far enough to do rounded caps without any visible clipping.
// There is _some_ clipping, but we can't see it ;)
// If we want to do it properly, we would extend the radius for rounded caps too.
if is_cap_triangle && is_at_pointy_arrow_end {
// We extend the cap triangle far enough to handle triangle caps.
center_position += quad_dir * (triangle_cap_length * select(-1.0, 1.0, is_right_triangle));
pos = center_position;
} else {
pos = center_position + (active_radius * top_bottom) * dir_up;
pos = center_position + (strip_radius * top_bottom * 0.99) * dir_up;
}

// Extend the line for rendering smooth joints, as well as round start/end caps.
let is_at_inner_joint = !is_cap_triangle && !is_first_quad_after_cap && !is_last_quad_before_cap;
let is_at_quad_with_round_capped_start = !is_at_quad_end && is_first_quad_after_cap && has_any_flag(strip_data.flags, FLAG_CAP_START_ROUND);
let is_at_quad_with_round_capped_end = is_at_quad_end && is_last_quad_before_cap && has_any_flag(strip_data.flags, FLAG_CAP_END_ROUND);
if is_at_inner_joint || is_at_quad_with_round_capped_start || is_at_quad_with_round_capped_end {
let left_right_offset = quad_dir * strip_radius * select(-1.0, 1.0, is_at_quad_end);
pos += left_right_offset;
}

// Output, transform to projection space and done.
var out: VertexOut;
out.position = apply_depth_offset(frame.projection_from_world * vec4f(pos, 1.0), batch.depth_offset);
out.position_world = pos;
out.center_position = center_position;
out.round_cap_circle_center = round_cap_circle_center;
out.color = strip_data.color;
out.active_radius = active_radius;
out.fragment_flags = strip_data.flags &
(FLAG_COLOR_GRADIENT | (u32(is_cap_triangle) * select(FLAG_CAP_START_ROUND, FLAG_CAP_END_ROUND, is_right_triangle)));
out.rounded_inner_line_begin = rounded_inner_line_begin;
out.rounded_inner_line_end = rounded_inner_line_end;
out.rounded_inner_line_radius = strip_radius;
out.fragment_flags = fragment_flags;
out.picking_instance_id = strip_data.picking_instance_id;

return out;
}

fn distance_to_line_sq(pos: vec3f, line_a: vec3f, line_b: vec3f) -> f32 {
let a_to_pos = pos - line_a;
let a_to_b = line_b - line_a;
let h = saturate(dot(a_to_pos, a_to_b) / dot(a_to_b, a_to_b));
let to_line = a_to_pos - a_to_b * h;
return dot(to_line, to_line);
}

fn distance_to_line(pos: vec3f, line_a: vec3f, line_b: vec3f) -> f32 {
return sqrt(distance_to_line_sq(pos, line_a, line_b));
}

fn compute_coverage(in: VertexOut) -> f32 {
var coverage = 1.0;
if has_any_flag(in.fragment_flags, FLAG_CAP_START_ROUND | FLAG_CAP_END_ROUND) {
let distance_to_skeleton = length(in.position_world - in.round_cap_circle_center);

if !has_any_flag(in.fragment_flags, FLAG_CAP_TRIANGLE) {
let distance_to_skeleton = distance_to_line(in.position_world, in.rounded_inner_line_begin, in.rounded_inner_line_end);
let pixel_world_size = approx_pixel_world_size_at(length(in.position_world - frame.camera_position));

// It's important that we do antialias both inwards and outwards of the exact border.
// If we do only outwards, rectangle outlines won't line up nicely
let half_pixel_world_size = pixel_world_size * 0.5;
let signed_distance_to_border = distance_to_skeleton - in.active_radius;
let signed_distance_to_border = distance_to_skeleton - in.rounded_inner_line_radius;
coverage = 1.0 - saturate((signed_distance_to_border + half_pixel_world_size) / pixel_world_size);
}

// Debugging hack: An offset here makes the geometry visible!
//return coverage + 0.1;
return coverage;
}

Expand All @@ -317,9 +351,8 @@ fn fs_main(in: VertexOut) -> @location(0) vec4f {
// TODO(andreas): lighting setup
var shading = 1.0;
if has_any_flag(in.fragment_flags, FLAG_COLOR_GRADIENT) {
let to_center = in.position_world - in.center_position;
let relative_distance_to_center_sq = dot(to_center, to_center) / (in.active_radius * in.active_radius);
shading = max(0.2, 1.0 - relative_distance_to_center_sq) * 0.9;
let distance_to_inner = distance_to_line_sq(in.position_world, in.rounded_inner_line_begin, in.rounded_inner_line_end);
shading = max(0.2, 1.0 - distance_to_inner / (in.rounded_inner_line_radius * in.rounded_inner_line_radius)) * 0.9;
}

return vec4f(in.color.rgb * shading, coverage);
Expand Down
115 changes: 52 additions & 63 deletions crates/viewer/re_renderer/src/line_drawable_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,7 @@ impl<'ctx> LineDrawableBuilder<'ctx> {
}

pub fn default_box_flags() -> LineStripFlags {
LineStripFlags::FLAG_CAP_END_ROUND
| LineStripFlags::FLAG_CAP_START_ROUND
| LineStripFlags::FLAG_CAP_END_EXTEND_OUTWARDS
| LineStripFlags::FLAG_CAP_START_EXTEND_OUTWARDS
LineStripFlags::FLAGS_OUTWARD_EXTENDING_ROUND_CAPS
}
}

Expand Down Expand Up @@ -307,7 +304,6 @@ impl<'a, 'ctx> LineBatchBuilder<'a, 'ctx> {

/// Add box outlines from a unit cube transformed by `transform`.
///
/// Internally adds 12 line segments with rounded line heads.
/// Disables color gradient since we don't support gradients in this setup yet (i.e. enabling them does not look good)
#[inline]
pub fn add_box_outline_from_transform(
Expand All @@ -324,36 +320,15 @@ impl<'a, 'ctx> LineBatchBuilder<'a, 'ctx> {
transform.transform_point3(glam::vec3(0.5, 0.5, -0.5)),
transform.transform_point3(glam::vec3(0.5, 0.5, 0.5)),
];
self.add_segments(
[
// bottom:
(corners[0b000], corners[0b001]),
(corners[0b000], corners[0b010]),
(corners[0b011], corners[0b001]),
(corners[0b011], corners[0b010]),
// top:
(corners[0b100], corners[0b101]),
(corners[0b100], corners[0b110]),
(corners[0b111], corners[0b101]),
(corners[0b111], corners[0b110]),
// sides:
(corners[0b000], corners[0b100]),
(corners[0b001], corners[0b101]),
(corners[0b010], corners[0b110]),
(corners[0b011], corners[0b111]),
]
.into_iter(),
)
.flags(LineDrawableBuilder::default_box_flags())
self.add_box_from_corners(corners)
}

/// Add box outlines.
///
/// Internally adds 12 line segments with rounded line heads.
/// Internally a single closed line strip.
/// Disables color gradient since we don't support gradients in this setup yet (i.e. enabling them does not look good)
///
/// Returns None for empty and non-finite boxes.
#[inline]
pub fn add_box_outline(
&mut self,
bbox: &re_math::BoundingBox,
Expand All @@ -362,35 +337,54 @@ impl<'a, 'ctx> LineBatchBuilder<'a, 'ctx> {
return None;
}

let corners = bbox.corners();
Some(
self.add_segments(
[
// bottom:
(corners[0b000], corners[0b001]),
(corners[0b000], corners[0b010]),
(corners[0b011], corners[0b001]),
(corners[0b011], corners[0b010]),
// top:
(corners[0b100], corners[0b101]),
(corners[0b100], corners[0b110]),
(corners[0b111], corners[0b101]),
(corners[0b111], corners[0b110]),
// sides:
(corners[0b000], corners[0b100]),
(corners[0b001], corners[0b101]),
(corners[0b010], corners[0b110]),
(corners[0b011], corners[0b111]),
]
.into_iter(),
)
.flags(LineDrawableBuilder::default_box_flags()),
Some(self.add_box_from_corners(bbox.corners()))
}

fn add_box_from_corners(&mut self, corners: [glam::Vec3; 8]) -> LineStripBuilder<'_, 'ctx> {
let mut strip_index = self.0.strips_buffer.len() as u32;

// Bottom plus connection to top.
self.add_vertices(
[
// bottom loop
corners[0b000],
corners[0b001],
corners[0b011],
corners[0b010],
corners[0b000],
// joined to top loop
corners[0b100],
corners[0b101],
corners[0b111],
corners[0b110],
corners[0b100],
]
.into_iter(),
strip_index,
)
.ok_or_log_error_once();
strip_index += 1;

// remaining side edges.
for line in [
[corners[0b001], corners[0b101]],
[corners[0b010], corners[0b110]],
[corners[0b011], corners[0b111]],
] {
self.add_vertices(line.into_iter(), strip_index)
.ok_or_log_error_once();
strip_index += 1;
}

let num_strips_added = 4;
let num_vertices_added = 10 + 3 * 2;
self.create_strip_builder(num_strips_added, num_vertices_added)
.flags(LineDrawableBuilder::default_box_flags())
}

/// Add rectangle outlines.
///
/// Internally adds 4 line segments with rounded line heads.
/// Internally adds a single linestrip with 5 vertices.
/// Disables color gradient since we don't support gradients in this setup yet (i.e. enabling them does not look good)
#[inline]
pub fn add_rectangle_outline(
Expand All @@ -399,18 +393,13 @@ impl<'a, 'ctx> LineBatchBuilder<'a, 'ctx> {
extent_u: glam::Vec3,
extent_v: glam::Vec3,
) -> LineStripBuilder<'_, 'ctx> {
self.add_segments(
self.add_strip(
[
(top_left_corner, top_left_corner + extent_u),
(
top_left_corner + extent_u,
top_left_corner + extent_u + extent_v,
),
(
top_left_corner + extent_u + extent_v,
top_left_corner + extent_v,
),
(top_left_corner + extent_v, top_left_corner),
top_left_corner,
top_left_corner + extent_u,
top_left_corner + extent_u + extent_v,
top_left_corner + extent_v,
top_left_corner,
]
.into_iter(),
)
Expand Down
Loading
Loading