From 9930df83ed42008f7eb2c02cc7350040f0250c2e Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 21 Oct 2024 23:54:09 +0100 Subject: [PATCH] UI borders and outlines clipping fix (#16044) # Objective fixes #15502 Clipped borders and outlines aren't drawn correctly. ### Borders aren't clipped Spawn two nodes with the same dimensions and border thickness, but clip on of the nodes so that only its top left quarter is visible: clip You can see that instead of clipping the border, instead the border is scaled to fit inside of the unclipped section. ```rust use bevy::color::palettes::css::BLUE; use bevy::prelude::*; use bevy::winit::WinitSettings; fn main() { App::new() .add_plugins(DefaultPlugins) .insert_resource(WinitSettings::desktop_app()) .add_systems(Startup, setup) .run(); } fn setup(mut commands: Commands) { commands.spawn(Camera2d); commands .spawn(Node { width: Val::Percent(100.), height: Val::Percent(100.), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..Default::default() }) .with_children(|commands| { commands .spawn(Node { column_gap: Val::Px(10.), ..Default::default() }) .with_children(|commands| { commands .spawn(Node { width: Val::Px(100.), height: Val::Px(100.), overflow: Overflow::clip(), ..Default::default() }) .with_child(( Node { position_type: PositionType::Absolute, width: Val::Px(100.), height: Val::Px(100.), border: UiRect::all(Val::Px(10.)), ..Default::default() }, BackgroundColor(Color::WHITE), BorderColor(BLUE.into()), )); commands .spawn(Node { width: Val::Px(50.), height: Val::Px(50.), overflow: Overflow::clip(), ..Default::default() }) .with_child(( Node { position_type: PositionType::Absolute, width: Val::Px(100.), height: Val::Px(100.), border: UiRect::all(Val::Px(10.)), ..Default::default() }, BackgroundColor(Color::WHITE), BorderColor(BLUE.into()), )); }); }); } ``` You can also see this problem in the `overflow` example. If you hover over any of the clipped nodes you'll see that the outline only wraps the visible section of the node ### Outlines are clipped incorrectly A UI nodes Outline's are drawn outside of its bounds, so applying the local clipping rect to the outline doesn't make any sense. Instead an `Outline` should be clipped using its parent's clipping rect. ## Solution * Pass the `point` value into the vertex shader instead of calculating it in the shader. * In `extract_uinode_borders` use the parents clipping rect when clipping outlines. The extra parameter isn't a great solution I think, but I wanted to fix borders for the 0.15 release and this is the most minimal approach I could think of without replacing the whole shader and prepare function. ## Showcase clipp --------- Co-authored-by: Alice Cecile --- crates/bevy_ui/src/render/mod.rs | 21 ++++++++++++++++++++- crates/bevy_ui/src/render/pipeline.rs | 2 ++ crates/bevy_ui/src/render/ui.wgsl | 8 +------- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 4e1ac3069a95c..041ed36072987 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -16,6 +16,7 @@ use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; use bevy_ecs::entity::{EntityHashMap, EntityHashSet}; use bevy_ecs::prelude::*; +use bevy_hierarchy::Parent; use bevy_math::{FloatOrd, Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4Swizzles}; use bevy_render::render_phase::ViewSortedRenderPhases; use bevy_render::sync_world::MainEntity; @@ -404,8 +405,10 @@ pub fn extract_uinode_borders( Option<&CalculatedClip>, Option<&TargetCamera>, AnyOf<(&BorderColor, &Outline)>, + Option<&Parent>, )>, >, + parent_clip_query: Extract>, mapping: Extract>, ) { let image = AssetId::::default(); @@ -418,6 +421,7 @@ pub fn extract_uinode_borders( maybe_clip, maybe_camera, (maybe_border_color, maybe_outline), + maybe_parent, ) in &uinode_query { let Some(camera_entity) = maybe_camera @@ -471,6 +475,9 @@ pub fn extract_uinode_borders( if let Some(outline) = maybe_outline { let outline_size = uinode.outlined_node_size(); + let parent_clip = + maybe_parent.and_then(|parent| parent_clip_query.get(parent.get()).ok()); + extracted_uinodes.uinodes.insert( commands.spawn(TemporaryRenderEntity).id(), ExtractedUiNode { @@ -481,7 +488,7 @@ pub fn extract_uinode_borders( ..Default::default() }, image, - clip: maybe_clip.map(|clip| clip.clip), + clip: parent_clip.map(|clip| clip.clip), camera_entity: render_camera_entity, item: ExtractedUiItem::Node { transform: global_transform.compute_matrix(), @@ -768,6 +775,8 @@ struct UiVertex { pub border: [f32; 4], /// Size of the UI node. pub size: [f32; 2], + /// Position relative to the center of the UI node. + pub point: [f32; 2], } #[derive(Resource)] @@ -998,6 +1007,7 @@ pub fn prepare_uinodes( // Specify the corners of the node let positions = QUAD_VERTEX_POSITIONS .map(|pos| (*transform * (pos * rect_size).extend(1.)).xyz()); + let points = QUAD_VERTEX_POSITIONS.map(|pos| pos.xy() * rect_size.xy()); // Calculate the effect of clipping // Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads) @@ -1031,6 +1041,13 @@ pub fn prepare_uinodes( positions[3] + positions_diff[3].extend(0.), ]; + let points = [ + points[0] + positions_diff[0], + points[1] + positions_diff[1], + points[2] + positions_diff[2], + points[3] + positions_diff[3], + ]; + let transformed_rect_size = transform.transform_vector3(rect_size); // Don't try to cull nodes that have a rotation @@ -1113,6 +1130,7 @@ pub fn prepare_uinodes( ], border: [border.left, border.top, border.right, border.bottom], size: rect_size.xy().into(), + point: points[i].into(), }); } @@ -1215,6 +1233,7 @@ pub fn prepare_uinodes( radius: [0.0; 4], border: [0.0; 4], size: size.into(), + point: [0.0; 2], }); } diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index 715a1984b9482..0e99461092ffb 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -72,6 +72,8 @@ impl SpecializedRenderPipeline for UiPipeline { VertexFormat::Float32x4, // border size VertexFormat::Float32x2, + // position relative to the center + VertexFormat::Float32x2, ], ); let shader_defs = if key.anti_alias { diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index c46e0b4926279..d73e7bd92949b 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -38,6 +38,7 @@ fn vertex( // x: left, y: top, z: right, w: bottom. @location(5) border: vec4, @location(6) size: vec2, + @location(7) point: vec2, ) -> VertexOutput { var out: VertexOutput; out.uv = vertex_uv; @@ -47,13 +48,6 @@ fn vertex( out.radius = radius; out.size = size; out.border = border; - var point = 0.49999 * size; - if (flags & RIGHT_VERTEX) == 0u { - point.x *= -1.; - } - if (flags & BOTTOM_VERTEX) == 0u { - point.y *= -1.; - } out.point = point; return out;