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); | ||||
| 
 | ||||
|     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 = 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); | ||||
| } | ||||
| 
 | ||||
|     // 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> { | ||||
|     if enabled(in.flags, BORDER) { | ||||
|         return draw(in);     | ||||
|     } else { | ||||
|         return draw_background(in); | ||||
|     } | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Mike
						Mike