1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-27 23:13:13 -04:00

Introduce dithering to reduce banding (#4497)

This PR introduces dithering in the egui_glow and egui_wgpu backends to
reduce banding artifacts.

It's based on the approach mentioned in #4493 with the small difference
that the amount of noise is scaled down slightly to avoid dithering
colors that can be represented exactly. This keeps flat surfaces clean.

Exaggerated dithering to show what is happening:
![Screenshot from 2024-05-14
19-09-48](https://github.com/emilk/egui/assets/293536/75782b83-9023-4cb2-99f7-a24e15fdefcc)

Subtle dithering as commited.
![Screenshot from 2024-05-14
19-13-40](https://github.com/emilk/egui/assets/293536/eb904698-a6ec-494a-952b-447e9a49bfda)

Closes #4493
This commit is contained in:
Jonas Wagner
2024-07-08 09:57:11 +02:00
committed by GitHub
parent fcd02bd7d1
commit b283b8a560
13 changed files with 141 additions and 18 deletions

View File

@@ -369,6 +369,15 @@ pub struct NativeOptions {
/// The folder where `eframe` will store the app state. If not set, eframe will get the paths
/// from [directories].
pub persistence_path: Option<std::path::PathBuf>,
/// Controls whether to apply dithering to minimize banding artifacts.
///
/// Dithering assumes an sRGB output and thus will apply noise to any input value that lies between
/// two 8bit values after applying the sRGB OETF function, i.e. if it's not a whole 8bit value in "gamma space".
/// This means that only inputs from texture interpolation and vertex colors should be affected in practice.
///
/// Defaults to true.
pub dithering: bool,
}
#[cfg(not(target_arch = "wasm32"))]
@@ -429,6 +438,8 @@ impl Default for NativeOptions {
persist_window: true,
persistence_path: None,
dithering: true,
}
}
}
@@ -466,6 +477,15 @@ pub struct WebOptions {
/// Configures wgpu instance/device/adapter/surface creation and renderloop.
#[cfg(feature = "wgpu")]
pub wgpu_options: egui_wgpu::WgpuConfiguration,
/// Controls whether to apply dithering to minimize banding artifacts.
///
/// Dithering assumes an sRGB output and thus will apply noise to any input value that lies between
/// two 8bit values after applying the sRGB OETF function, i.e. if it's not a whole 8bit value in "gamma space".
/// This means that only inputs from texture interpolation and vertex colors should be affected in practice.
///
/// Defaults to true.
pub dithering: bool,
}
#[cfg(target_arch = "wasm32")]
@@ -481,6 +501,8 @@ impl Default for WebOptions {
#[cfg(feature = "wgpu")]
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
dithering: true,
}
}
}

View File

@@ -184,7 +184,12 @@ impl GlowWinitApp {
}))
};
let painter = egui_glow::Painter::new(gl, "", native_options.shader_version)?;
let painter = egui_glow::Painter::new(
gl,
"",
native_options.shader_version,
native_options.dithering,
)?;
Ok((glutin_window_context, painter))
}

View File

@@ -194,6 +194,7 @@ impl WgpuWinitApp {
self.native_options.stencil_buffer,
),
self.native_options.viewport.transparent.unwrap_or(false),
self.native_options.dithering,
);
let window = Arc::new(window);

View File

@@ -26,7 +26,7 @@ impl WebPainterGlow {
#[allow(clippy::arc_with_non_send_sync)]
let gl = std::sync::Arc::new(gl);
let painter = egui_glow::Painter::new(gl, shader_prefix, None)
let painter = egui_glow::Painter::new(gl, shader_prefix, None, options.dithering)
.map_err(|err| format!("Error starting glow painter: {err}"))?;
Ok(Self { canvas, painter })

View File

@@ -169,10 +169,16 @@ impl WebPainterWgpu {
let depth_format = egui_wgpu::depth_format_from_bits(options.depth_buffer, 0);
let render_state =
RenderState::create(&options.wgpu_options, &instance, &surface, depth_format, 1)
.await
.map_err(|err| err.to_string())?;
let render_state = RenderState::create(
&options.wgpu_options,
&instance,
&surface,
depth_format,
1,
options.dithering,
)
.await
.map_err(|err| err.to_string())?;
let surface_configuration = wgpu::SurfaceConfiguration {
format: render_state.target_format,