-
Notifications
You must be signed in to change notification settings - Fork 451
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
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
273fc3b
round joints without breaking existing line drawing features
Wumpf e85ed48
compute round joints & round caps on the same codepath
Wumpf aa1b897
add line radius boost to geo line outlines
Wumpf d842d6f
Fix artifacts due to flag int flag flat interpolation
Wumpf ba6dd0b
make line joint extension code easier to read
Wumpf afab8b9
fixup doc block in lines.rs
Wumpf 3647e98
boxes & rectangles use now line strips
Wumpf d205819
disable alpha-to-coverage blending workaround if blending is disabled
Wumpf 62c0023
make closed line loops appear closed
Wumpf 4a32166
make frustum outline consist of a few loops rather than individual se…
Wumpf 9ed19cf
fix box/rect line builder reserve
Wumpf b0b01e6
make typos happy
Wumpf 1ce5c3d
fix typo, use saturate over clamp
Wumpf 4446387
fix slight rendering artifact on camera frustum
Wumpf 9d6b7ee
Merge branch 'main' into andreas/line-joints
Wumpf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,6 +47,9 @@ const FLAG_CAP_START_EXTEND_OUTWARDS: u32 = 32u; | |
const FLAG_COLOR_GRADIENT: u32 = 64u; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
@@ -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, | ||
|
@@ -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: | ||
|
@@ -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. | ||
|
@@ -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. | ||
|
@@ -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; | ||
} | ||
|
||
|
@@ -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); | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.