Fix various bugs with UI rounded borders (#13523)
# Objective - Fixes #13503 - Fix other various bugs I noticed while debugging above issue. ## Solution - Change the antialiasing(AA) method. It was using fwidth which is the derivative between pixels, but there were a lot of artifacts being added from this. So just use the sdf value. This aa method probably isn't as smooth looking, but better than having artifacts. Below is a visualization of the fwidth.  - Use the internal sdf for drawing the background instead of the external sdf and extract the border for these type of nodes. This fixed 2 bugs, one with the background coloring the AA pixels on the edge of rounded borders. And also allows for the border to use a transparent color. - Don't extract borders if all the widths are zero. ## Testing - played a bunch with the example in the linked issue. This PR:  Main:  - ran the `borders` and `rounded_borders` examples --- ## Changelog - Fixed various antialiasing issues to do with rounded ui borders. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Andreas Weibye <13300393+Weibye@users.noreply.github.com>
This commit is contained in:
parent
f67ae29338
commit
cef31ffdd9
@ -199,8 +199,11 @@ pub fn extract_uinode_background_colors(
|
||||
Option<&TargetCamera>,
|
||||
&BackgroundColor,
|
||||
Option<&BorderRadius>,
|
||||
&Style,
|
||||
Option<&Parent>,
|
||||
)>,
|
||||
>,
|
||||
node_query: Extract<Query<&Node>>,
|
||||
) {
|
||||
for (
|
||||
entity,
|
||||
@ -211,6 +214,8 @@ pub fn extract_uinode_background_colors(
|
||||
camera,
|
||||
background_color,
|
||||
border_radius,
|
||||
style,
|
||||
parent,
|
||||
) in &uinode_query
|
||||
{
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
@ -232,6 +237,23 @@ pub fn extract_uinode_background_colors(
|
||||
// so we have to divide by `UiScale` to get the size of the UI viewport.
|
||||
/ ui_scale.0;
|
||||
|
||||
// Both vertical and horizontal percentage border values are calculated based on the width of the parent node
|
||||
// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>
|
||||
let parent_width = parent
|
||||
.and_then(|parent| node_query.get(parent.get()).ok())
|
||||
.map(|parent_node| parent_node.size().x)
|
||||
.unwrap_or(ui_logical_viewport_size.x);
|
||||
let left =
|
||||
resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size);
|
||||
let right =
|
||||
resolve_border_thickness(style.border.right, parent_width, ui_logical_viewport_size);
|
||||
let top =
|
||||
resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size);
|
||||
let bottom =
|
||||
resolve_border_thickness(style.border.bottom, parent_width, ui_logical_viewport_size);
|
||||
|
||||
let border = [left, top, right, bottom];
|
||||
|
||||
let border_radius = if let Some(border_radius) = border_radius {
|
||||
resolve_border_radius(
|
||||
border_radius,
|
||||
@ -259,7 +281,7 @@ pub fn extract_uinode_background_colors(
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
camera_entity,
|
||||
border: [0.; 4],
|
||||
border,
|
||||
border_radius,
|
||||
node_type: NodeType::Rect,
|
||||
},
|
||||
@ -267,6 +289,7 @@ pub fn extract_uinode_background_colors(
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn extract_uinode_images(
|
||||
mut commands: Commands,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
@ -285,11 +308,25 @@ pub fn extract_uinode_images(
|
||||
Option<&TextureAtlas>,
|
||||
Option<&ComputedTextureSlices>,
|
||||
Option<&BorderRadius>,
|
||||
Option<&Parent>,
|
||||
&Style,
|
||||
)>,
|
||||
>,
|
||||
node_query: Extract<Query<&Node>>,
|
||||
) {
|
||||
for (uinode, transform, view_visibility, clip, camera, image, atlas, slices, border_radius) in
|
||||
&uinode_query
|
||||
for (
|
||||
uinode,
|
||||
transform,
|
||||
view_visibility,
|
||||
clip,
|
||||
camera,
|
||||
image,
|
||||
atlas,
|
||||
slices,
|
||||
border_radius,
|
||||
parent,
|
||||
style,
|
||||
) in &uinode_query
|
||||
{
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
else {
|
||||
@ -342,6 +379,23 @@ pub fn extract_uinode_images(
|
||||
// so we have to divide by `UiScale` to get the size of the UI viewport.
|
||||
/ ui_scale.0;
|
||||
|
||||
// Both vertical and horizontal percentage border values are calculated based on the width of the parent node
|
||||
// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>
|
||||
let parent_width = parent
|
||||
.and_then(|parent| node_query.get(parent.get()).ok())
|
||||
.map(|parent_node| parent_node.size().x)
|
||||
.unwrap_or(ui_logical_viewport_size.x);
|
||||
let left =
|
||||
resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size);
|
||||
let right =
|
||||
resolve_border_thickness(style.border.right, parent_width, ui_logical_viewport_size);
|
||||
let top =
|
||||
resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size);
|
||||
let bottom =
|
||||
resolve_border_thickness(style.border.bottom, parent_width, ui_logical_viewport_size);
|
||||
|
||||
let border = [left, top, right, bottom];
|
||||
|
||||
let border_radius = if let Some(border_radius) = border_radius {
|
||||
resolve_border_radius(
|
||||
border_radius,
|
||||
@ -366,7 +420,7 @@ pub fn extract_uinode_images(
|
||||
flip_x: image.flip_x,
|
||||
flip_y: image.flip_y,
|
||||
camera_entity,
|
||||
border: [0.; 4],
|
||||
border,
|
||||
border_radius,
|
||||
node_type: NodeType::Rect,
|
||||
},
|
||||
@ -513,6 +567,11 @@ pub fn extract_uinode_borders(
|
||||
|
||||
let border = [left, top, right, bottom];
|
||||
|
||||
// don't extract border if no border
|
||||
if left == 0.0 && top == 0.0 && right == 0.0 && bottom == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let border_radius = resolve_border_radius(
|
||||
border_radius,
|
||||
node.size(),
|
||||
|
||||
@ -79,7 +79,7 @@ fn sd_rounded_box(point: vec2<f32>, size: vec2<f32>, corner_radii: vec4<f32>) ->
|
||||
// If 0.0 < y then select bottom left (w) and bottom right corner radius (z).
|
||||
// Else select top left (x) and top right corner radius (y).
|
||||
let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.y);
|
||||
// w and z are swapped so that both pairs are in left to right order, otherwise this second
|
||||
// w and z are swapped above so that both pairs are in left to right order, otherwise this second
|
||||
// select statement would return the incorrect value for the bottom pair.
|
||||
let radius = select(rs.x, rs.y, 0.0 < point.x);
|
||||
// Vector from the corner closest to the point, to the point.
|
||||
@ -120,6 +120,12 @@ fn sd_inset_rounded_box(point: vec2<f32>, size: vec2<f32>, radius: vec4<f32>, in
|
||||
return sd_rounded_box(inner_point, inner_size, r);
|
||||
}
|
||||
|
||||
// get alpha for antialiasing for sdf
|
||||
fn antialias(distance: f32) -> f32 {
|
||||
// Using the fwidth(distance) was causing artifacts, so just use the distance.
|
||||
return clamp(0.0, 1.0, 0.5 - distance);
|
||||
}
|
||||
|
||||
fn draw(in: VertexOutput) -> vec4<f32> {
|
||||
let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv);
|
||||
|
||||
@ -145,32 +151,31 @@ fn draw(in: VertexOutput) -> vec4<f32> {
|
||||
// outside the outside edge, or inside the inner edge have positive signed distance.
|
||||
let border_distance = max(external_distance, -internal_distance);
|
||||
|
||||
// The `fwidth` function returns an approximation of the rate of change of the signed distance
|
||||
// value that is used to ensure that the smooth alpha transition created by smoothstep occurs
|
||||
// over a range of distance values that is proportional to how quickly the distance is changing.
|
||||
let fborder = fwidth(border_distance);
|
||||
let fexternal = fwidth(external_distance);
|
||||
// At external edges with no border, `border_distance` is equal to zero.
|
||||
// This select statement ensures we only perform anti-aliasing where a non-zero width border
|
||||
// is present, otherwise an outline about the external boundary would be drawn even without
|
||||
// a border.
|
||||
let t = select(1.0 - step(0.0, border_distance), antialias(border_distance), external_distance < internal_distance);
|
||||
|
||||
if enabled(in.flags, BORDER) {
|
||||
// The item is a border
|
||||
// Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here.
|
||||
return vec4(color.rgb, color.a * t);
|
||||
}
|
||||
|
||||
// At external edges with no border, `border_distance` is equal to zero.
|
||||
// This select statement ensures we only perform anti-aliasing where a non-zero width border
|
||||
// is present, otherwise an outline about the external boundary would be drawn even without
|
||||
// a border.
|
||||
let t = 1. - select(step(0.0, border_distance), smoothstep(0.0, fborder, border_distance), external_distance < internal_distance);
|
||||
|
||||
// Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here.
|
||||
return vec4(color.rgb, color.a * t);
|
||||
}
|
||||
|
||||
// The item is a rectangle, draw normally with anti-aliasing at the edges.
|
||||
let t = 1. - smoothstep(0.0, fexternal, external_distance);
|
||||
fn draw_background(in: VertexOutput) -> vec4<f32> {
|
||||
let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv);
|
||||
let color = select(in.color, in.color * texture_color, enabled(in.flags, TEXTURED));
|
||||
|
||||
// When drawing the background only draw the internal area and not the border.
|
||||
let internal_distance = sd_inset_rounded_box(in.point, in.size, in.radius, in.border);
|
||||
let t = antialias(internal_distance);
|
||||
return vec4(color.rgb, color.a * t);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return draw(in);
|
||||
if enabled(in.flags, BORDER) {
|
||||
return draw(in);
|
||||
} else {
|
||||
return draw_background(in);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user