diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 67bcd56a80..0b646367aa 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -25,7 +25,7 @@ use ui_texture_slice_pipeline::UiTextureSlicerPlugin; use crate::graph::{NodeUi, SubGraphUi}; use crate::{ BackgroundColor, BorderColor, CalculatedClip, DefaultUiCamera, Display, Node, Outline, Style, - TargetCamera, UiImage, UiScale, Val, + TargetCamera, UiAntiAlias, UiImage, UiScale, Val, }; use bevy_app::prelude::*; @@ -608,13 +608,15 @@ pub fn extract_default_ui_camera_view( mut commands: Commands, mut transparent_render_phases: ResMut>, ui_scale: Extract>, - query: Extract, With)>>>, + query: Extract< + Query<(Entity, &Camera, Option<&UiAntiAlias>), Or<(With, With)>>, + >, mut live_entities: Local, ) { live_entities.clear(); let scale = ui_scale.0.recip(); - for (entity, camera) in &query { + for (entity, camera, ui_anti_alias) in &query { // ignore inactive cameras if !camera.is_active { continue; @@ -660,9 +662,12 @@ pub fn extract_default_ui_camera_view( color_grading: Default::default(), }) .id(); - commands + let entity_commands = commands .get_or_spawn(entity) .insert(DefaultCameraView(default_camera_view)); + if let Some(ui_anti_alias) = ui_anti_alias { + entity_commands.insert(*ui_anti_alias); + } transparent_render_phases.insert_or_clear(entity); live_entities.insert(entity); @@ -837,13 +842,14 @@ pub fn queue_uinodes( ui_pipeline: Res, mut pipelines: ResMut>, mut transparent_render_phases: ResMut>, - mut views: Query<(Entity, &ExtractedView)>, + mut views: Query<(Entity, &ExtractedView, Option<&UiAntiAlias>)>, pipeline_cache: Res, draw_functions: Res>, ) { let draw_function = draw_functions.read().id::(); for (entity, extracted_uinode) in extracted_uinodes.uinodes.iter() { - let Ok((view_entity, view)) = views.get_mut(extracted_uinode.camera_entity) else { + let Ok((view_entity, view, ui_anti_alias)) = views.get_mut(extracted_uinode.camera_entity) + else { continue; }; @@ -854,7 +860,10 @@ pub fn queue_uinodes( let pipeline = pipelines.specialize( &pipeline_cache, &ui_pipeline, - UiPipelineKey { hdr: view.hdr }, + UiPipelineKey { + hdr: view.hdr, + anti_alias: matches!(ui_anti_alias, None | Some(UiAntiAlias::On)), + }, ); transparent_phase.add(TransparentUi { draw_function, diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index e31dc3bcfb..715a1984b9 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -48,6 +48,7 @@ impl FromWorld for UiPipeline { #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct UiPipelineKey { pub hdr: bool, + pub anti_alias: bool, } impl SpecializedRenderPipeline for UiPipeline { @@ -73,7 +74,11 @@ impl SpecializedRenderPipeline for UiPipeline { VertexFormat::Float32x2, ], ); - let shader_defs = Vec::new(); + let shader_defs = if key.anti_alias { + vec!["ANTI_ALIAS".into()] + } else { + Vec::new() + }; RenderPipelineDescriptor { vertex: VertexState { diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 91d22067a2..ad82783d7f 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -150,11 +150,15 @@ fn draw(in: VertexOutput, texture_color: vec4) -> vec4 { // outside the outside edge, or inside the inner edge have positive signed distance. let border_distance = max(external_distance, -internal_distance); +#ifdef ANTI_ALIAS // 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); +#else + let t = 1.0 - step(0.0, border_distance); +#endif // Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here. return vec4(color.rgb, saturate(color.a * t)); @@ -165,7 +169,13 @@ fn draw_background(in: VertexOutput, texture_color: vec4) -> vec4 { // 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); + +#ifdef ANTI_ALIAS let t = antialias(internal_distance); +#else + let t = 1.0 - step(0.0, internal_distance); +#endif + return vec4(color.rgb, saturate(color.a * t)); } diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 9fbe8c4437..8a2502e7d8 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -2381,3 +2381,29 @@ impl<'w, 's> DefaultUiCamera<'w, 's> { }) } } + +/// Marker for controlling whether Ui is rendered with or without anti-aliasing +/// in a camera. By default, Ui is always anti-aliased. +/// +/// ``` +/// use bevy_core_pipeline::prelude::*; +/// use bevy_ecs::prelude::*; +/// use bevy_ui::prelude::*; +/// +/// fn spawn_camera(mut commands: Commands) { +/// commands.spawn(( +/// Camera2dBundle::default(), +/// // This will cause all Ui in this camera to be rendered without +/// // anti-aliasing +/// UiAntiAlias::Off, +/// )); +/// } +/// ``` +#[derive(Component, Clone, Copy, Default, Debug, Reflect, Eq, PartialEq)] +pub enum UiAntiAlias { + /// UI will render with anti-aliasing + #[default] + On, + /// UI will render without anti-aliasing + Off, +}