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>,
|
Option<&TargetCamera>,
|
||||||
&BackgroundColor,
|
&BackgroundColor,
|
||||||
Option<&BorderRadius>,
|
Option<&BorderRadius>,
|
||||||
|
&Style,
|
||||||
|
Option<&Parent>,
|
||||||
)>,
|
)>,
|
||||||
>,
|
>,
|
||||||
|
node_query: Extract<Query<&Node>>,
|
||||||
) {
|
) {
|
||||||
for (
|
for (
|
||||||
entity,
|
entity,
|
||||||
@ -211,6 +214,8 @@ pub fn extract_uinode_background_colors(
|
|||||||
camera,
|
camera,
|
||||||
background_color,
|
background_color,
|
||||||
border_radius,
|
border_radius,
|
||||||
|
style,
|
||||||
|
parent,
|
||||||
) in &uinode_query
|
) in &uinode_query
|
||||||
{
|
{
|
||||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
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.
|
// so we have to divide by `UiScale` to get the size of the UI viewport.
|
||||||
/ ui_scale.0;
|
/ 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 {
|
let border_radius = if let Some(border_radius) = border_radius {
|
||||||
resolve_border_radius(
|
resolve_border_radius(
|
||||||
border_radius,
|
border_radius,
|
||||||
@ -259,7 +281,7 @@ pub fn extract_uinode_background_colors(
|
|||||||
flip_x: false,
|
flip_x: false,
|
||||||
flip_y: false,
|
flip_y: false,
|
||||||
camera_entity,
|
camera_entity,
|
||||||
border: [0.; 4],
|
border,
|
||||||
border_radius,
|
border_radius,
|
||||||
node_type: NodeType::Rect,
|
node_type: NodeType::Rect,
|
||||||
},
|
},
|
||||||
@ -267,6 +289,7 @@ pub fn extract_uinode_background_colors(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn extract_uinode_images(
|
pub fn extract_uinode_images(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||||
@ -285,11 +308,25 @@ pub fn extract_uinode_images(
|
|||||||
Option<&TextureAtlas>,
|
Option<&TextureAtlas>,
|
||||||
Option<&ComputedTextureSlices>,
|
Option<&ComputedTextureSlices>,
|
||||||
Option<&BorderRadius>,
|
Option<&BorderRadius>,
|
||||||
|
Option<&Parent>,
|
||||||
|
&Style,
|
||||||
)>,
|
)>,
|
||||||
>,
|
>,
|
||||||
|
node_query: Extract<Query<&Node>>,
|
||||||
) {
|
) {
|
||||||
for (uinode, transform, view_visibility, clip, camera, image, atlas, slices, border_radius) in
|
for (
|
||||||
&uinode_query
|
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())
|
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||||
else {
|
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.
|
// so we have to divide by `UiScale` to get the size of the UI viewport.
|
||||||
/ ui_scale.0;
|
/ 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 {
|
let border_radius = if let Some(border_radius) = border_radius {
|
||||||
resolve_border_radius(
|
resolve_border_radius(
|
||||||
border_radius,
|
border_radius,
|
||||||
@ -366,7 +420,7 @@ pub fn extract_uinode_images(
|
|||||||
flip_x: image.flip_x,
|
flip_x: image.flip_x,
|
||||||
flip_y: image.flip_y,
|
flip_y: image.flip_y,
|
||||||
camera_entity,
|
camera_entity,
|
||||||
border: [0.; 4],
|
border,
|
||||||
border_radius,
|
border_radius,
|
||||||
node_type: NodeType::Rect,
|
node_type: NodeType::Rect,
|
||||||
},
|
},
|
||||||
@ -513,6 +567,11 @@ pub fn extract_uinode_borders(
|
|||||||
|
|
||||||
let border = [left, top, right, bottom];
|
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(
|
let border_radius = resolve_border_radius(
|
||||||
border_radius,
|
border_radius,
|
||||||
node.size(),
|
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).
|
// 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).
|
// Else select top left (x) and top right corner radius (y).
|
||||||
let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.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.
|
// select statement would return the incorrect value for the bottom pair.
|
||||||
let radius = select(rs.x, rs.y, 0.0 < point.x);
|
let radius = select(rs.x, rs.y, 0.0 < point.x);
|
||||||
// Vector from the corner closest to the point, to the point.
|
// 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);
|
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> {
|
fn draw(in: VertexOutput) -> vec4<f32> {
|
||||||
let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv);
|
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.
|
// outside the outside edge, or inside the inner edge have positive signed distance.
|
||||||
let border_distance = max(external_distance, -internal_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);
|
|
||||||
|
|
||||||
if enabled(in.flags, BORDER) {
|
|
||||||
// The item is a border
|
|
||||||
|
|
||||||
// At external edges with no border, `border_distance` is equal to zero.
|
// 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
|
// 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
|
// is present, otherwise an outline about the external boundary would be drawn even without
|
||||||
// a border.
|
// a border.
|
||||||
let t = 1. - select(step(0.0, border_distance), smoothstep(0.0, fborder, border_distance), external_distance < internal_distance);
|
let t = select(1.0 - step(0.0, border_distance), antialias(border_distance), external_distance < internal_distance);
|
||||||
|
|
||||||
// Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here.
|
// Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here.
|
||||||
return vec4(color.rgb, color.a * t);
|
return vec4(color.rgb, color.a * t);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The item is a rectangle, draw normally with anti-aliasing at the edges.
|
fn draw_background(in: VertexOutput) -> vec4<f32> {
|
||||||
let t = 1. - smoothstep(0.0, fexternal, external_distance);
|
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);
|
return vec4(color.rgb, color.a * t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
if enabled(in.flags, BORDER) {
|
||||||
return draw(in);
|
return draw(in);
|
||||||
|
} else {
|
||||||
|
return draw_background(in);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user