Skip to content

Commit cef31ff

Browse files
hymmalice-i-cecileWeibye
authored
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. ![image](https://github.com/bevyengine/bevy/assets/2180432/4e475ad0-c9d0-4a40-af39-5f4422a78392) - 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: ![image](https://github.com/bevyengine/bevy/assets/2180432/d7797e0e-e348-4daa-8646-554dc2032523) Main: ![image](https://github.com/bevyengine/bevy/assets/2180432/4d46c17e-a12d-4e20-aaef-0ffc950cefe2) - ran the `borders` and `rounded_borders` examples --- ## Changelog - Fixed various antialiasing issues to do with rounded ui borders. --------- Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: Andreas Weibye <[email protected]>
1 parent f67ae29 commit cef31ff

File tree

2 files changed

+89
-25
lines changed

2 files changed

+89
-25
lines changed

crates/bevy_ui/src/render/mod.rs

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,11 @@ pub fn extract_uinode_background_colors(
199199
Option<&TargetCamera>,
200200
&BackgroundColor,
201201
Option<&BorderRadius>,
202+
&Style,
203+
Option<&Parent>,
202204
)>,
203205
>,
206+
node_query: Extract<Query<&Node>>,
204207
) {
205208
for (
206209
entity,
@@ -211,6 +214,8 @@ pub fn extract_uinode_background_colors(
211214
camera,
212215
background_color,
213216
border_radius,
217+
style,
218+
parent,
214219
) in &uinode_query
215220
{
216221
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
@@ -232,6 +237,23 @@ pub fn extract_uinode_background_colors(
232237
// so we have to divide by `UiScale` to get the size of the UI viewport.
233238
/ ui_scale.0;
234239

240+
// Both vertical and horizontal percentage border values are calculated based on the width of the parent node
241+
// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>
242+
let parent_width = parent
243+
.and_then(|parent| node_query.get(parent.get()).ok())
244+
.map(|parent_node| parent_node.size().x)
245+
.unwrap_or(ui_logical_viewport_size.x);
246+
let left =
247+
resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size);
248+
let right =
249+
resolve_border_thickness(style.border.right, parent_width, ui_logical_viewport_size);
250+
let top =
251+
resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size);
252+
let bottom =
253+
resolve_border_thickness(style.border.bottom, parent_width, ui_logical_viewport_size);
254+
255+
let border = [left, top, right, bottom];
256+
235257
let border_radius = if let Some(border_radius) = border_radius {
236258
resolve_border_radius(
237259
border_radius,
@@ -259,14 +281,15 @@ pub fn extract_uinode_background_colors(
259281
flip_x: false,
260282
flip_y: false,
261283
camera_entity,
262-
border: [0.; 4],
284+
border,
263285
border_radius,
264286
node_type: NodeType::Rect,
265287
},
266288
);
267289
}
268290
}
269291

292+
#[allow(clippy::too_many_arguments)]
270293
pub fn extract_uinode_images(
271294
mut commands: Commands,
272295
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
@@ -285,11 +308,25 @@ pub fn extract_uinode_images(
285308
Option<&TextureAtlas>,
286309
Option<&ComputedTextureSlices>,
287310
Option<&BorderRadius>,
311+
Option<&Parent>,
312+
&Style,
288313
)>,
289314
>,
315+
node_query: Extract<Query<&Node>>,
290316
) {
291-
for (uinode, transform, view_visibility, clip, camera, image, atlas, slices, border_radius) in
292-
&uinode_query
317+
for (
318+
uinode,
319+
transform,
320+
view_visibility,
321+
clip,
322+
camera,
323+
image,
324+
atlas,
325+
slices,
326+
border_radius,
327+
parent,
328+
style,
329+
) in &uinode_query
293330
{
294331
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
295332
else {
@@ -342,6 +379,23 @@ pub fn extract_uinode_images(
342379
// so we have to divide by `UiScale` to get the size of the UI viewport.
343380
/ ui_scale.0;
344381

382+
// Both vertical and horizontal percentage border values are calculated based on the width of the parent node
383+
// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>
384+
let parent_width = parent
385+
.and_then(|parent| node_query.get(parent.get()).ok())
386+
.map(|parent_node| parent_node.size().x)
387+
.unwrap_or(ui_logical_viewport_size.x);
388+
let left =
389+
resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size);
390+
let right =
391+
resolve_border_thickness(style.border.right, parent_width, ui_logical_viewport_size);
392+
let top =
393+
resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size);
394+
let bottom =
395+
resolve_border_thickness(style.border.bottom, parent_width, ui_logical_viewport_size);
396+
397+
let border = [left, top, right, bottom];
398+
345399
let border_radius = if let Some(border_radius) = border_radius {
346400
resolve_border_radius(
347401
border_radius,
@@ -366,7 +420,7 @@ pub fn extract_uinode_images(
366420
flip_x: image.flip_x,
367421
flip_y: image.flip_y,
368422
camera_entity,
369-
border: [0.; 4],
423+
border,
370424
border_radius,
371425
node_type: NodeType::Rect,
372426
},
@@ -513,6 +567,11 @@ pub fn extract_uinode_borders(
513567

514568
let border = [left, top, right, bottom];
515569

570+
// don't extract border if no border
571+
if left == 0.0 && top == 0.0 && right == 0.0 && bottom == 0.0 {
572+
continue;
573+
}
574+
516575
let border_radius = resolve_border_radius(
517576
border_radius,
518577
node.size(),

crates/bevy_ui/src/render/ui.wgsl

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ fn sd_rounded_box(point: vec2<f32>, size: vec2<f32>, corner_radii: vec4<f32>) ->
7979
// If 0.0 < y then select bottom left (w) and bottom right corner radius (z).
8080
// Else select top left (x) and top right corner radius (y).
8181
let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.y);
82-
// w and z are swapped so that both pairs are in left to right order, otherwise this second
82+
// w and z are swapped above so that both pairs are in left to right order, otherwise this second
8383
// select statement would return the incorrect value for the bottom pair.
8484
let radius = select(rs.x, rs.y, 0.0 < point.x);
8585
// 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
120120
return sd_rounded_box(inner_point, inner_size, r);
121121
}
122122

123+
// get alpha for antialiasing for sdf
124+
fn antialias(distance: f32) -> f32 {
125+
// Using the fwidth(distance) was causing artifacts, so just use the distance.
126+
return clamp(0.0, 1.0, 0.5 - distance);
127+
}
128+
123129
fn draw(in: VertexOutput) -> vec4<f32> {
124130
let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv);
125131

@@ -145,32 +151,31 @@ fn draw(in: VertexOutput) -> vec4<f32> {
145151
// outside the outside edge, or inside the inner edge have positive signed distance.
146152
let border_distance = max(external_distance, -internal_distance);
147153

148-
// The `fwidth` function returns an approximation of the rate of change of the signed distance
149-
// value that is used to ensure that the smooth alpha transition created by smoothstep occurs
150-
// over a range of distance values that is proportional to how quickly the distance is changing.
151-
let fborder = fwidth(border_distance);
152-
let fexternal = fwidth(external_distance);
153-
154-
if enabled(in.flags, BORDER) {
155-
// The item is a border
154+
// At external edges with no border, `border_distance` is equal to zero.
155+
// This select statement ensures we only perform anti-aliasing where a non-zero width border
156+
// is present, otherwise an outline about the external boundary would be drawn even without
157+
// a border.
158+
let t = select(1.0 - step(0.0, border_distance), antialias(border_distance), external_distance < internal_distance);
156159

157-
// At external edges with no border, `border_distance` is equal to zero.
158-
// This select statement ensures we only perform anti-aliasing where a non-zero width border
159-
// is present, otherwise an outline about the external boundary would be drawn even without
160-
// a border.
161-
let t = 1. - select(step(0.0, border_distance), smoothstep(0.0, fborder, border_distance), external_distance < internal_distance);
162-
163-
// Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here.
164-
return vec4(color.rgb, color.a * t);
165-
}
160+
// Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here.
161+
return vec4(color.rgb, color.a * t);
162+
}
166163

167-
// The item is a rectangle, draw normally with anti-aliasing at the edges.
168-
let t = 1. - smoothstep(0.0, fexternal, external_distance);
164+
fn draw_background(in: VertexOutput) -> vec4<f32> {
165+
let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv);
166+
let color = select(in.color, in.color * texture_color, enabled(in.flags, TEXTURED));
169167

168+
// When drawing the background only draw the internal area and not the border.
169+
let internal_distance = sd_inset_rounded_box(in.point, in.size, in.radius, in.border);
170+
let t = antialias(internal_distance);
170171
return vec4(color.rgb, color.a * t);
171172
}
172173

173174
@fragment
174175
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
175-
return draw(in);
176+
if enabled(in.flags, BORDER) {
177+
return draw(in);
178+
} else {
179+
return draw_background(in);
180+
}
176181
}

0 commit comments

Comments
 (0)