Add `RectShape::blur_width` to implement shadows (#4267)
This is mostly a refactor, but has some performance benefits: * We (re)use the same tessellator as for everything else, leading to less allocations * We cull shapes before rendering them Adding `RectShape::blur_width` means it can also be used for other effects, such as glow.
This commit is contained in:
parent
73dbfd689b
commit
a541e021aa
|
|
@ -291,9 +291,8 @@ impl Frame {
|
||||||
if shadow == Default::default() {
|
if shadow == Default::default() {
|
||||||
frame_shape
|
frame_shape
|
||||||
} else {
|
} else {
|
||||||
let shadow = shadow.tessellate(outer_rect, rounding);
|
let shadow = shadow.as_shape(outer_rect, rounding);
|
||||||
let shadow = Shape::Mesh(shadow);
|
Shape::Vec(vec![Shape::from(shadow), frame_shape])
|
||||||
Shape::Vec(vec![shadow, frame_shape])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -762,6 +762,7 @@ pub fn paint_texture_at(
|
||||||
rounding: options.rounding,
|
rounding: options.rounding,
|
||||||
fill: options.tint,
|
fill: options.tint,
|
||||||
stroke: Stroke::NONE,
|
stroke: Stroke::NONE,
|
||||||
|
blur_width: 0.0,
|
||||||
fill_texture_id: texture.id,
|
fill_texture_id: texture.id,
|
||||||
uv: options.uv,
|
uv: options.uv,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -739,14 +739,8 @@ impl<'a> Slider<'a> {
|
||||||
};
|
};
|
||||||
let v = v + Vec2::splat(visuals.expansion);
|
let v = v + Vec2::splat(visuals.expansion);
|
||||||
let rect = Rect::from_center_size(center, 2.0 * v);
|
let rect = Rect::from_center_size(center, 2.0 * v);
|
||||||
ui.painter().add(epaint::RectShape {
|
ui.painter()
|
||||||
fill: visuals.bg_fill,
|
.rect(rect, visuals.rounding, visuals.bg_fill, visuals.fg_stroke);
|
||||||
stroke: visuals.fg_stroke,
|
|
||||||
rect,
|
|
||||||
rounding: visuals.rounding,
|
|
||||||
fill_texture_id: Default::default(),
|
|
||||||
uv: Rect::ZERO,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use emath::NumExt as _;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// The color and fuzziness of a fuzzy shape.
|
/// The color and fuzziness of a fuzzy shape.
|
||||||
|
|
@ -37,11 +35,10 @@ impl Shadow {
|
||||||
color: Color32::TRANSPARENT,
|
color: Color32::TRANSPARENT,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn tessellate(&self, rect: Rect, rounding: impl Into<Rounding>) -> Mesh {
|
/// The argument is the rectangle of the shadow caster.
|
||||||
|
pub fn as_shape(&self, rect: Rect, rounding: impl Into<Rounding>) -> RectShape {
|
||||||
// tessellator.clip_rect = clip_rect; // TODO(emilk): culling
|
// tessellator.clip_rect = clip_rect; // TODO(emilk): culling
|
||||||
|
|
||||||
use crate::tessellator::*;
|
|
||||||
|
|
||||||
let Self {
|
let Self {
|
||||||
offset,
|
offset,
|
||||||
blur,
|
blur,
|
||||||
|
|
@ -50,40 +47,9 @@ impl Shadow {
|
||||||
} = *self;
|
} = *self;
|
||||||
|
|
||||||
let rect = rect.translate(offset).expand(spread);
|
let rect = rect.translate(offset).expand(spread);
|
||||||
|
let rounding = rounding.into() + Rounding::same(spread.abs());
|
||||||
|
|
||||||
// We simulate a blurry shadow by tessellating a solid rectangle using a very large feathering.
|
RectShape::filled(rect, rounding, color).with_blur_width(blur)
|
||||||
// Feathering is usually used to make the edges of a shape softer for anti-aliasing.
|
|
||||||
// The tessellator can't handle blurring/feathering larger than the smallest side of the rect.
|
|
||||||
// Thats because the tessellator approximate very thin rectangles as line segments,
|
|
||||||
// and these line segments don't have rounded corners.
|
|
||||||
// When the feathering is small (the size of a pixel), this is usually fine,
|
|
||||||
// but here we have a huge feathering to simulate blur,
|
|
||||||
// so we need to avoid this optimization in the tessellator,
|
|
||||||
// which is also why we add this rather big epsilon:
|
|
||||||
let eps = 0.1;
|
|
||||||
let blur = blur.at_most(rect.size().min_elem() - eps).at_least(0.0);
|
|
||||||
|
|
||||||
// TODO(emilk): if blur <= 0, return a simple `Shape::Rect` instead of using the tessellator
|
|
||||||
|
|
||||||
let rounding_expansion = spread.abs() + 0.5 * blur;
|
|
||||||
let rounding = rounding.into() + Rounding::same(rounding_expansion);
|
|
||||||
|
|
||||||
let rect = RectShape::filled(rect, rounding, color);
|
|
||||||
let pixels_per_point = 1.0; // doesn't matter here
|
|
||||||
let font_tex_size = [1; 2]; // unused since we are not tessellating text.
|
|
||||||
let mut tessellator = Tessellator::new(
|
|
||||||
pixels_per_point,
|
|
||||||
TessellationOptions {
|
|
||||||
feathering: true,
|
|
||||||
feathering_size_in_pixels: blur * pixels_per_point,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
font_tex_size,
|
|
||||||
vec![],
|
|
||||||
);
|
|
||||||
let mut mesh = Mesh::default();
|
|
||||||
tessellator.tessellate_rect(&rect, &mut mesh);
|
|
||||||
mesh
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How much larger than the parent rect are we in each direction?
|
/// How much larger than the parent rect are we in each direction?
|
||||||
|
|
|
||||||
|
|
@ -668,6 +668,14 @@ pub struct RectShape {
|
||||||
/// The thickness and color of the outline.
|
/// The thickness and color of the outline.
|
||||||
pub stroke: Stroke,
|
pub stroke: Stroke,
|
||||||
|
|
||||||
|
/// If larger than zero, the edges of the rectangle
|
||||||
|
/// (for both fill and stroke) will be blurred.
|
||||||
|
///
|
||||||
|
/// This can be used to produce shadows and glow effects.
|
||||||
|
///
|
||||||
|
/// The blur is currently implemented using a simple linear blur in sRGBA gamma space.
|
||||||
|
pub blur_width: f32,
|
||||||
|
|
||||||
/// If the rect should be filled with a texture, which one?
|
/// If the rect should be filled with a texture, which one?
|
||||||
///
|
///
|
||||||
/// The texture is multiplied with [`Self::fill`].
|
/// The texture is multiplied with [`Self::fill`].
|
||||||
|
|
@ -695,6 +703,7 @@ impl RectShape {
|
||||||
rounding: rounding.into(),
|
rounding: rounding.into(),
|
||||||
fill: fill_color.into(),
|
fill: fill_color.into(),
|
||||||
stroke: stroke.into(),
|
stroke: stroke.into(),
|
||||||
|
blur_width: 0.0,
|
||||||
fill_texture_id: Default::default(),
|
fill_texture_id: Default::default(),
|
||||||
uv: Rect::ZERO,
|
uv: Rect::ZERO,
|
||||||
}
|
}
|
||||||
|
|
@ -711,6 +720,7 @@ impl RectShape {
|
||||||
rounding: rounding.into(),
|
rounding: rounding.into(),
|
||||||
fill: fill_color.into(),
|
fill: fill_color.into(),
|
||||||
stroke: Default::default(),
|
stroke: Default::default(),
|
||||||
|
blur_width: 0.0,
|
||||||
fill_texture_id: Default::default(),
|
fill_texture_id: Default::default(),
|
||||||
uv: Rect::ZERO,
|
uv: Rect::ZERO,
|
||||||
}
|
}
|
||||||
|
|
@ -723,18 +733,32 @@ impl RectShape {
|
||||||
rounding: rounding.into(),
|
rounding: rounding.into(),
|
||||||
fill: Default::default(),
|
fill: Default::default(),
|
||||||
stroke: stroke.into(),
|
stroke: stroke.into(),
|
||||||
|
blur_width: 0.0,
|
||||||
fill_texture_id: Default::default(),
|
fill_texture_id: Default::default(),
|
||||||
uv: Rect::ZERO,
|
uv: Rect::ZERO,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If larger than zero, the edges of the rectangle
|
||||||
|
/// (for both fill and stroke) will be blurred.
|
||||||
|
///
|
||||||
|
/// This can be used to produce shadows and glow effects.
|
||||||
|
///
|
||||||
|
/// The blur is currently implemented using a simple linear blur in `sRGBA` gamma space.
|
||||||
|
#[inline]
|
||||||
|
pub fn with_blur_width(mut self, blur_width: f32) -> Self {
|
||||||
|
self.blur_width = blur_width;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// The visual bounding rectangle (includes stroke width)
|
/// The visual bounding rectangle (includes stroke width)
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn visual_bounding_rect(&self) -> Rect {
|
pub fn visual_bounding_rect(&self) -> Rect {
|
||||||
if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
|
if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
|
||||||
Rect::NOTHING
|
Rect::NOTHING
|
||||||
} else {
|
} else {
|
||||||
self.rect.expand(self.stroke.width / 2.0)
|
self.rect
|
||||||
|
.expand((self.stroke.width + self.blur_width) / 2.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) {
|
||||||
rounding: _,
|
rounding: _,
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
|
blur_width: _,
|
||||||
fill_texture_id: _,
|
fill_texture_id: _,
|
||||||
uv: _,
|
uv: _,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1503,9 +1503,10 @@ impl Tessellator {
|
||||||
pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) {
|
pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) {
|
||||||
let RectShape {
|
let RectShape {
|
||||||
mut rect,
|
mut rect,
|
||||||
rounding,
|
mut rounding,
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
|
mut blur_width,
|
||||||
fill_texture_id,
|
fill_texture_id,
|
||||||
uv,
|
uv,
|
||||||
} = *rect;
|
} = *rect;
|
||||||
|
|
@ -1524,6 +1525,29 @@ impl Tessellator {
|
||||||
rect.min = rect.min.at_least(pos2(-1e7, -1e7));
|
rect.min = rect.min.at_least(pos2(-1e7, -1e7));
|
||||||
rect.max = rect.max.at_most(pos2(1e7, 1e7));
|
rect.max = rect.max.at_most(pos2(1e7, 1e7));
|
||||||
|
|
||||||
|
let old_feathering = self.feathering;
|
||||||
|
|
||||||
|
if old_feathering < blur_width {
|
||||||
|
// We accomplish the blur by using a larger-than-normal feathering.
|
||||||
|
// Feathering is usually used to make the edges of a shape softer for anti-aliasing.
|
||||||
|
|
||||||
|
// The tessellator can't handle blurring/feathering larger than the smallest side of the rect.
|
||||||
|
// Thats because the tessellator approximate very thin rectangles as line segments,
|
||||||
|
// and these line segments don't have rounded corners.
|
||||||
|
// When the feathering is small (the size of a pixel), this is usually fine,
|
||||||
|
// but here we have a huge feathering to simulate blur,
|
||||||
|
// so we need to avoid this optimization in the tessellator,
|
||||||
|
// which is also why we add this rather big epsilon:
|
||||||
|
let eps = 0.1;
|
||||||
|
blur_width = blur_width
|
||||||
|
.at_most(rect.size().min_elem() - eps)
|
||||||
|
.at_least(0.0);
|
||||||
|
|
||||||
|
rounding += Rounding::same(0.5 * blur_width);
|
||||||
|
|
||||||
|
self.feathering = self.feathering.max(blur_width);
|
||||||
|
}
|
||||||
|
|
||||||
if rect.width() < self.feathering {
|
if rect.width() < self.feathering {
|
||||||
// Very thin - approximate by a vertical line-segment:
|
// Very thin - approximate by a vertical line-segment:
|
||||||
let line = [rect.center_top(), rect.center_bottom()];
|
let line = [rect.center_top(), rect.center_bottom()];
|
||||||
|
|
@ -1566,6 +1590,8 @@ impl Tessellator {
|
||||||
|
|
||||||
path.stroke_closed(self.feathering, stroke, out);
|
path.stroke_closed(self.feathering, stroke, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.feathering = old_feathering; // restore
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tessellate a single [`TextShape`] into a [`Mesh`].
|
/// Tessellate a single [`TextShape`] into a [`Mesh`].
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue