Skip to content

Commit 988770a

Browse files
ickshonpemockersf
authored andcommitted
UI anti-aliasing fix (#16181)
UI Anti-aliasing is incorrectly implemented. It always uses an edge radius of 0.25 logical pixels, and ignores the physical resolution. For low dpi screens 0.25 is is too low and on higher dpi screens the physical edge radius is much too large, resulting in visual artifacts. Multiply the distance by the scale factor in the `antialias` function so that the edge radius stays constant in physical pixels. To see the problem really clearly run the button example with `UiScale` set really high. With `UiScale(25.)` on main if you examine the button's border you can see a thick gradient fading away from the edges: <img width="127" alt="edgg" src="https://github.com/user-attachments/assets/7c852030-c0e8-4aef-8d3e-768cb2464cab"> With this PR the edges are sharp and smooth at all scale factors: <img width="127" alt="edge" src="https://github.com/user-attachments/assets/b3231140-1bbc-4a4f-a1d3-dde21f287988">
1 parent 572f0c1 commit 988770a

File tree

8 files changed

+38
-12
lines changed

8 files changed

+38
-12
lines changed

crates/bevy_render/src/camera/camera.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,10 @@ impl Camera {
441441

442442
#[inline]
443443
pub fn target_scaling_factor(&self) -> Option<f32> {
444-
self.computed.target_info.as_ref().map(|t| t.scale_factor)
444+
self.computed
445+
.target_info
446+
.as_ref()
447+
.map(|t: &RenderTargetInfo| t.scale_factor)
445448
}
446449

447450
/// The projection matrix computed using this camera's [`CameraProjection`].

crates/bevy_ui/src/render/box_shadow.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ pub fn queue_shadows(
371371
),
372372
batch_range: 0..0,
373373
extra_index: PhaseItemExtraIndex::NONE,
374+
inverse_scale_factor: 1.,
374375
});
375376
}
376377
}

crates/bevy_ui/src/render/mod.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,11 @@ const UI_CAMERA_TRANSFORM_OFFSET: f32 = -0.1;
525525
#[derive(Component)]
526526
pub struct DefaultCameraView(pub Entity);
527527

528+
#[derive(Component)]
529+
pub struct ExtractedAA {
530+
pub scale_factor: f32,
531+
}
532+
528533
/// Extracts all UI elements associated with a camera into the render world.
529534
pub fn extract_default_ui_camera_view(
530535
mut commands: Commands,
@@ -552,7 +557,7 @@ pub fn extract_default_ui_camera_view(
552557
commands
553558
.get_entity(entity)
554559
.expect("Camera entity wasn't synced.")
555-
.remove::<(DefaultCameraView, UiAntiAlias, UiBoxShadowSamples)>();
560+
.remove::<(DefaultCameraView, ExtractedAA, UiBoxShadowSamples)>();
556561
continue;
557562
}
558563

@@ -563,10 +568,12 @@ pub fn extract_default_ui_camera_view(
563568
..
564569
}),
565570
Some(physical_size),
571+
Some(scale_factor),
566572
) = (
567573
camera.logical_viewport_size(),
568574
camera.physical_viewport_rect(),
569575
camera.physical_viewport_size(),
576+
camera.target_scaling_factor(),
570577
) {
571578
// use a projection matrix with the origin in the top left instead of the bottom left that comes with OrthographicProjection
572579
let projection_matrix = Mat4::orthographic_rh(
@@ -577,6 +584,7 @@ pub fn extract_default_ui_camera_view(
577584
0.0,
578585
UI_CAMERA_FAR,
579586
);
587+
580588
let default_camera_view = commands
581589
.spawn((
582590
ExtractedView {
@@ -603,8 +611,10 @@ pub fn extract_default_ui_camera_view(
603611
.get_entity(entity)
604612
.expect("Camera entity wasn't synced.");
605613
entity_commands.insert(DefaultCameraView(default_camera_view));
606-
if let Some(ui_anti_alias) = ui_anti_alias {
607-
entity_commands.insert(*ui_anti_alias);
614+
if ui_anti_alias != Some(&UiAntiAlias::Off) {
615+
entity_commands.insert(ExtractedAA {
616+
scale_factor: (scale_factor * ui_scale.0),
617+
});
608618
}
609619
if let Some(shadow_samples) = shadow_samples {
610620
entity_commands.insert(*shadow_samples);
@@ -782,6 +792,7 @@ struct UiVertex {
782792
pub size: [f32; 2],
783793
/// Position relative to the center of the UI node.
784794
pub point: [f32; 2],
795+
pub inverse_scale_factor: f32,
785796
}
786797

787798
#[derive(Resource)]
@@ -832,13 +843,13 @@ pub fn queue_uinodes(
832843
ui_pipeline: Res<UiPipeline>,
833844
mut pipelines: ResMut<SpecializedRenderPipelines<UiPipeline>>,
834845
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
835-
mut views: Query<(Entity, &ExtractedView, Option<&UiAntiAlias>)>,
846+
mut views: Query<(Entity, &ExtractedView, Option<&ExtractedAA>)>,
836847
pipeline_cache: Res<PipelineCache>,
837848
draw_functions: Res<DrawFunctions<TransparentUi>>,
838849
) {
839850
let draw_function = draw_functions.read().id::<DrawUi>();
840851
for (entity, extracted_uinode) in extracted_uinodes.uinodes.iter() {
841-
let Ok((view_entity, view, ui_anti_alias)) = views.get_mut(extracted_uinode.camera_entity)
852+
let Ok((view_entity, view, extracted_aa)) = views.get_mut(extracted_uinode.camera_entity)
842853
else {
843854
continue;
844855
};
@@ -852,7 +863,7 @@ pub fn queue_uinodes(
852863
&ui_pipeline,
853864
UiPipelineKey {
854865
hdr: view.hdr,
855-
anti_alias: matches!(ui_anti_alias, None | Some(UiAntiAlias::On)),
866+
anti_alias: extracted_aa.is_some(),
856867
},
857868
);
858869
transparent_phase.add(TransparentUi {
@@ -866,6 +877,7 @@ pub fn queue_uinodes(
866877
// batch_range will be calculated in prepare_uinodes
867878
batch_range: 0..0,
868879
extra_index: PhaseItemExtraIndex::NONE,
880+
inverse_scale_factor: extracted_aa.map(|aa| aa.scale_factor).unwrap_or(1.),
869881
});
870882
}
871883
}
@@ -1136,6 +1148,7 @@ pub fn prepare_uinodes(
11361148
border: [border.left, border.top, border.right, border.bottom],
11371149
size: rect_size.xy().into(),
11381150
point: points[i].into(),
1151+
inverse_scale_factor: item.inverse_scale_factor,
11391152
});
11401153
}
11411154

@@ -1239,6 +1252,7 @@ pub fn prepare_uinodes(
12391252
border: [0.0; 4],
12401253
size: size.into(),
12411254
point: [0.0; 2],
1255+
inverse_scale_factor: item.inverse_scale_factor,
12421256
});
12431257
}
12441258

crates/bevy_ui/src/render/pipeline.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ impl SpecializedRenderPipeline for UiPipeline {
7474
VertexFormat::Float32x2,
7575
// position relative to the center
7676
VertexFormat::Float32x2,
77+
// inverse scale factor
78+
VertexFormat::Float32,
7779
],
7880
);
7981
let shader_defs = if key.anti_alias {

crates/bevy_ui/src/render/render_pass.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ pub struct TransparentUi {
9797
pub draw_function: DrawFunctionId,
9898
pub batch_range: Range<u32>,
9999
pub extra_index: PhaseItemExtraIndex,
100+
pub inverse_scale_factor: f32,
100101
}
101102

102103
impl PhaseItem for TransparentUi {
@@ -206,6 +207,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetUiTextureBindGroup<I>
206207
RenderCommandResult::Success
207208
}
208209
}
210+
209211
pub struct DrawUiNode;
210212
impl<P: PhaseItem> RenderCommand<P> for DrawUiNode {
211213
type Param = SRes<UiMeta>;

crates/bevy_ui/src/render/ui.wgsl

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ struct VertexOutput {
2222

2323
// Position relative to the center of the rectangle.
2424
@location(6) point: vec2<f32>,
25+
@location(7) @interpolate(flat) scale_factor: f32,
2526
@builtin(position) position: vec4<f32>,
2627
};
2728

@@ -39,6 +40,7 @@ fn vertex(
3940
@location(5) border: vec4<f32>,
4041
@location(6) size: vec2<f32>,
4142
@location(7) point: vec2<f32>,
43+
@location(8) scale_factor: f32,
4244
) -> VertexOutput {
4345
var out: VertexOutput;
4446
out.uv = vertex_uv;
@@ -49,6 +51,7 @@ fn vertex(
4951
out.size = size;
5052
out.border = border;
5153
out.point = point;
54+
out.scale_factor = scale_factor;
5255

5356
return out;
5457
}
@@ -115,10 +118,9 @@ fn sd_inset_rounded_box(point: vec2<f32>, size: vec2<f32>, radius: vec4<f32>, in
115118
}
116119

117120
// get alpha for antialiasing for sdf
118-
fn antialias(distance: f32) -> f32 {
121+
fn antialias(distance: f32, scale_factor: f32) -> f32 {
119122
// Using the fwidth(distance) was causing artifacts, so just use the distance.
120-
// This antialiases between the distance values of 0.25 and -0.25
121-
return clamp(0.0, 1.0, 0.5 - 2.0 * distance);
123+
return clamp(0.0, 1.0, (0.5 - scale_factor * distance));
122124
}
123125

124126
fn draw(in: VertexOutput, texture_color: vec4<f32>) -> vec4<f32> {
@@ -149,7 +151,7 @@ fn draw(in: VertexOutput, texture_color: vec4<f32>) -> vec4<f32> {
149151
// This select statement ensures we only perform anti-aliasing where a non-zero width border
150152
// is present, otherwise an outline about the external boundary would be drawn even without
151153
// a border.
152-
let t = select(1.0 - step(0.0, border_distance), antialias(border_distance), external_distance < internal_distance);
154+
let t = select(1.0 - step(0.0, border_distance), antialias(border_distance, in.scale_factor), external_distance < internal_distance);
153155
#else
154156
let t = 1.0 - step(0.0, border_distance);
155157
#endif
@@ -165,7 +167,7 @@ fn draw_background(in: VertexOutput, texture_color: vec4<f32>) -> vec4<f32> {
165167
let internal_distance = sd_inset_rounded_box(in.point, in.size, in.radius, in.border);
166168

167169
#ifdef ANTI_ALIAS
168-
let t = antialias(internal_distance);
170+
let t = antialias(internal_distance, in.scale_factor);
169171
#else
170172
let t = 1.0 - step(0.0, internal_distance);
171173
#endif

crates/bevy_ui/src/render/ui_material_pipeline.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,7 @@ pub fn queue_ui_material_nodes<M: UiMaterial>(
655655
),
656656
batch_range: 0..0,
657657
extra_index: PhaseItemExtraIndex::NONE,
658+
inverse_scale_factor: 1.,
658659
});
659660
}
660661
}

crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ pub fn queue_ui_slices(
370370
),
371371
batch_range: 0..0,
372372
extra_index: PhaseItemExtraIndex::NONE,
373+
inverse_scale_factor: 1.,
373374
});
374375
}
375376
}

0 commit comments

Comments
 (0)