mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 14:10:19 +00:00
Enable/disable UI anti-aliasing (#15170)
# Objective
Currently, UI is always rendered with anti-aliasing. This makes bevy's
UI completely unsuitable for art-styles that demands hard pixelated
edges, such as retro-style games.
## Solution
Add a component for disabling anti-aliasing in UI.
## Testing
In
[`examples/ui/button.rs`](15e246eff8/examples/ui/button.rs
),
add the component to the camera like this:
```rust
use bevy::{prelude::*, ui::prelude::*};
commands.spawn((Camera2dBundle::default(), UiAntiAlias::Off));
```
The rounded button will now render without anti-aliasing.
## Showcase
An example of a rounded UI node rendered without anti-aliasing, with and
without borders:
![image](https://github.com/user-attachments/assets/ea797e40-bdaa-4ede-a0d3-c9a7eab95b6e)
This commit is contained in:
parent
29c4c79342
commit
3efef59d83
4 changed files with 58 additions and 8 deletions
|
@ -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<ViewSortedRenderPhases<TransparentUi>>,
|
||||
ui_scale: Extract<Res<UiScale>>,
|
||||
query: Extract<Query<(Entity, &Camera), Or<(With<Camera2d>, With<Camera3d>)>>>,
|
||||
query: Extract<
|
||||
Query<(Entity, &Camera, Option<&UiAntiAlias>), Or<(With<Camera2d>, With<Camera3d>)>>,
|
||||
>,
|
||||
mut live_entities: Local<EntityHashSet>,
|
||||
) {
|
||||
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<UiPipeline>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<UiPipeline>>,
|
||||
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
|
||||
mut views: Query<(Entity, &ExtractedView)>,
|
||||
mut views: Query<(Entity, &ExtractedView, Option<&UiAntiAlias>)>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
draw_functions: Res<DrawFunctions<TransparentUi>>,
|
||||
) {
|
||||
let draw_function = draw_functions.read().id::<DrawUi>();
|
||||
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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -150,11 +150,15 @@ fn draw(in: VertexOutput, texture_color: vec4<f32>) -> vec4<f32> {
|
|||
// 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<f32>) -> vec4<f32> {
|
|||
|
||||
// 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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue