From e88a1f157b6d7a957c58793e9b1c5676edf41149 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Sat, 25 May 2024 18:34:09 -0700 Subject: [PATCH 1/9] fix sdf calculation --- crates/bevy_ui/src/render/ui.wgsl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index b23d89bdb6d12..bc5ffd6cab2f6 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -79,11 +79,14 @@ fn sd_rounded_box(point: vec2, size: vec2, corner_radii: vec4) -> // 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); + // The geometric size of the box is 1 greater than the pixel size. Since + // pixels are measured from their centers + let geometric_size = size + 1.0; // Vector from the corner closest to the point, to the point. - let corner_to_point = abs(point) - 0.5 * size; + let corner_to_point = abs(point) - 0.5 * geometric_size; // Vector from the center of the radius circle to the point. let q = corner_to_point + radius; // Length from center of the radius circle to the point, zeros a component if the point is not From 34b3463d10e4d0f50c9dd2aa477c01fc3a891689 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Sat, 25 May 2024 18:34:23 -0700 Subject: [PATCH 2/9] rework antialiasing --- crates/bevy_ui/src/render/ui.wgsl | 34 ++++++++++++++++++------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index bc5ffd6cab2f6..b237657bcb324 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -123,6 +123,23 @@ fn sd_inset_rounded_box(point: vec2, size: vec2, radius: vec4, in return sd_rounded_box(inner_point, inner_size, r); } +// get alpha for antialiasing for sdf +fn antialias(distance: f32) -> f32 { + let fborder = fwidth(distance) * 0.25; + let dx = dpdx(distance); + let dy = dpdy(distance); + + return smoothstep(fborder, -fborder, distance); + + // doesn't work well + // let distance_change = length(vec2(dpdx(distance), dpdy(distance))); + // let pixel_distance = distance / distance_change; + // return saturate(0.5 - pixel_distance); + + // return 1.0 - smoothstep(0.0, 1.0, distance); + +} + fn draw(in: VertexOutput) -> vec4 { let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv); @@ -148,29 +165,18 @@ fn draw(in: VertexOutput) -> vec4 { // 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); - if enabled(in.flags, BORDER) { // The item is a border - - // 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); + let t = antialias(border_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); - + let t = antialias(external_distance); return vec4(color.rgb, color.a * t); + } @fragment From 6aed9b38b9cd40d5a730073619880cc8aaa29b96 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Sun, 26 May 2024 12:02:01 -0700 Subject: [PATCH 3/9] fix antialiasing --- crates/bevy_ui/src/render/ui.wgsl | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index b237657bcb324..7fd1d212b5694 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -82,11 +82,8 @@ fn sd_rounded_box(point: vec2, size: vec2, corner_radii: vec4) -> // 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); - // The geometric size of the box is 1 greater than the pixel size. Since - // pixels are measured from their centers - let geometric_size = size + 1.0; // Vector from the corner closest to the point, to the point. - let corner_to_point = abs(point) - 0.5 * geometric_size; + let corner_to_point = abs(point) - 0.5 * size; // Vector from the center of the radius circle to the point. let q = corner_to_point + radius; // Length from center of the radius circle to the point, zeros a component if the point is not @@ -125,19 +122,8 @@ fn sd_inset_rounded_box(point: vec2, size: vec2, radius: vec4, in // get alpha for antialiasing for sdf fn antialias(distance: f32) -> f32 { - let fborder = fwidth(distance) * 0.25; - let dx = dpdx(distance); - let dy = dpdy(distance); - - return smoothstep(fborder, -fborder, distance); - - // doesn't work well - // let distance_change = length(vec2(dpdx(distance), dpdy(distance))); - // let pixel_distance = distance / distance_change; - // return saturate(0.5 - pixel_distance); - - // return 1.0 - smoothstep(0.0, 1.0, distance); - + // Using the fwidth was causing artifacts, so just use the distance. + return clamp(0.0, 1.0, 0.5 - distance); } fn draw(in: VertexOutput) -> vec4 { @@ -163,7 +149,7 @@ fn draw(in: VertexOutput) -> vec4 { // Signed distance from the border (the intersection of the rect with its border). // Points inside the border have negative signed distance. Any point outside the border, whether // outside the outside edge, or inside the inner edge have positive signed distance. - let border_distance = max(external_distance, -internal_distance); + let border_distance = select(max(external_distance, -internal_distance), -internal_distance, internal_distance <= 0); if enabled(in.flags, BORDER) { // The item is a border @@ -176,7 +162,6 @@ fn draw(in: VertexOutput) -> vec4 { // The item is a rectangle, draw normally with anti-aliasing at the edges. let t = antialias(external_distance); return vec4(color.rgb, color.a * t); - } @fragment From 518bc5cbabfdaf18860d54902712cf3c961eecea Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Sun, 26 May 2024 17:53:02 -0700 Subject: [PATCH 4/9] draw background with internal instead of external --- crates/bevy_ui/src/render/ui.wgsl | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 7fd1d212b5694..96f24bfa2570d 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -149,22 +149,30 @@ fn draw(in: VertexOutput) -> vec4 { // Signed distance from the border (the intersection of the rect with its border). // Points inside the border have negative signed distance. Any point outside the border, whether // outside the outside edge, or inside the inner edge have positive signed distance. - let border_distance = select(max(external_distance, -internal_distance), -internal_distance, internal_distance <= 0); + let border_distance = select(max(external_distance, -internal_distance), -internal_distance, internal_distance < 0); - if enabled(in.flags, BORDER) { - // The item is a border - let t = antialias(border_distance); + // The item is a border + let t = antialias(border_distance); - // Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here. - return vec4(color.rgb, color.a * t); - } + // Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here. + return vec4(color.rgb, color.a * t); +} + +fn draw_background(in: VertexOutput) -> vec4 { + let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv); + let color = select(in.color, in.color * texture_color, enabled(in.flags, TEXTURED)); - // The item is a rectangle, draw normally with anti-aliasing at the edges. - let t = antialias(external_distance); + /// 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 { - return draw(in); + if enabled(in.flags, BORDER) { + return draw(in); + } else { + return draw_background(in); + } } From ba14dd64229e611bec8b0afac02fdcd349d8eeeb Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Sun, 26 May 2024 17:53:33 -0700 Subject: [PATCH 5/9] use width in background and image nodes to clip --- crates/bevy_ui/src/render/mod.rs | 66 ++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index b71404c3a27eb..87c26c99af82d 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -199,8 +199,11 @@ pub fn extract_uinode_background_colors( Option<&TargetCamera>, &BackgroundColor, Option<&BorderRadius>, + &Style, + Option<&Parent>, )>, >, + node_query: Extract>, ) { 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 + // + 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, }, @@ -285,11 +307,25 @@ pub fn extract_uinode_images( Option<&TextureAtlas>, Option<&ComputedTextureSlices>, Option<&BorderRadius>, + Option<&Parent>, + &Style, )>, >, + node_query: Extract>, ) { - 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 +378,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 + // + 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 +419,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 +566,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(), From fc0b74d82076db56991bd4287e1f60c4b846b909 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Sun, 26 May 2024 18:07:32 -0700 Subject: [PATCH 6/9] revert change, not needed with antialiasing change --- crates/bevy_ui/src/render/ui.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 96f24bfa2570d..d95952ac88a50 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -149,7 +149,7 @@ fn draw(in: VertexOutput) -> vec4 { // Signed distance from the border (the intersection of the rect with its border). // Points inside the border have negative signed distance. Any point outside the border, whether // outside the outside edge, or inside the inner edge have positive signed distance. - let border_distance = select(max(external_distance, -internal_distance), -internal_distance, internal_distance < 0); + let border_distance = max(external_distance, -internal_distance); // The item is a border let t = antialias(border_distance); From 048500554ad324886a11603ee095b8d99dff9c85 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Sun, 26 May 2024 18:28:01 -0700 Subject: [PATCH 7/9] cleanup --- crates/bevy_ui/src/render/ui.wgsl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index d95952ac88a50..d8f8f37cb69dd 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -122,7 +122,7 @@ fn sd_inset_rounded_box(point: vec2, size: vec2, radius: vec4, in // get alpha for antialiasing for sdf fn antialias(distance: f32) -> f32 { - // Using the fwidth was causing artifacts, so just use the distance. + // Using the fwidth(distance) was causing artifacts, so just use the distance. return clamp(0.0, 1.0, 0.5 - distance); } @@ -151,8 +151,11 @@ fn draw(in: VertexOutput) -> vec4 { // outside the outside edge, or inside the inner edge have positive signed distance. let border_distance = max(external_distance, -internal_distance); - // The item is a border - let t = antialias(border_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); // Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here. return vec4(color.rgb, color.a * t); From 564bd26a76edfa3ab5b3e3e02bf8d1986b744386 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Sun, 26 May 2024 18:35:50 -0700 Subject: [PATCH 8/9] clippy --- crates/bevy_ui/src/render/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 87c26c99af82d..be0b0172182a3 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -289,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, From 656dd3ade64f31af87ae9e0fb592ae6218f7c3a3 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 27 May 2024 13:33:25 -0400 Subject: [PATCH 9/9] Fix comment style Co-authored-by: Andreas Weibye <13300393+Weibye@users.noreply.github.com> --- crates/bevy_ui/src/render/ui.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index d8f8f37cb69dd..b22c8615ff126 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -165,7 +165,7 @@ fn draw_background(in: VertexOutput) -> vec4 { 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. + // 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);