From 06bf9289276bdaa9397a03ccb54f02c9f1de2dcc Mon Sep 17 00:00:00 2001 From: Stepan Koltsov Date: Tue, 9 Jan 2024 00:46:01 +0000 Subject: [PATCH] Option to enable deterministic rendering (#11248) # Objective Issue #10243: rendering multiple triangles in the same place results in flickering. ## Solution Considered these alternatives: - `depth_bias` may not work, because of high number of entities, so creating a material per entity is practically not possible - rendering at slightly different positions does not work, because when camera is far, float rounding causes the same issues (edit: assuming we have to use the same `depth_bias`) - considered implementing deterministic operation like `query.par_iter().flat_map(...).collect()` to be used in `check_visibility` system (which would solve the issue since query is deterministic), and could not figure out how to make it as cheap as current approach with thread-local collectors (#11249) So adding an option to sort entities after `check_visibility` system run. Should not be too bad, because after visibility check, only a handful entities remain. This is probably not the only source of non-determinism in Bevy, but this is one I could find so far. At least it fixes the repro example. ## Changelog - `DeterministicRenderingConfig` option to enable deterministic rendering ## Test image --------- Co-authored-by: Alice Cecile --- Cargo.toml | 11 +++ crates/bevy_render/src/deterministic.rs | 14 +++ crates/bevy_render/src/lib.rs | 4 + crates/bevy_render/src/view/visibility/mod.rs | 7 ++ examples/3d/deterministic.rs | 87 +++++++++++++++++++ examples/README.md | 1 + 6 files changed, 124 insertions(+) create mode 100644 crates/bevy_render/src/deterministic.rs create mode 100644 examples/3d/deterministic.rs diff --git a/Cargo.toml b/Cargo.toml index 70677e717e..ab75415142 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -598,6 +598,17 @@ description = "Showcases different blend modes" category = "3D Rendering" wasm = true +[[example]] +name = "deterministic" +path = "examples/3d/deterministic.rs" +doc-scrape-examples = true + +[package.metadata.example.deterministic] +name = "Deterministic rendering" +description = "Stop flickering from z-fighting at a performance cost" +category = "3D Rendering" +wasm = true + [[example]] name = "lighting" path = "examples/3d/lighting.rs" diff --git a/crates/bevy_render/src/deterministic.rs b/crates/bevy_render/src/deterministic.rs new file mode 100644 index 0000000000..ec89116a5e --- /dev/null +++ b/crates/bevy_render/src/deterministic.rs @@ -0,0 +1,14 @@ +use bevy_ecs::system::Resource; + +/// Configure deterministic rendering to fix flickering due to z-fighting. +#[derive(Resource, Default)] +pub struct DeterministicRenderingConfig { + /// Sort visible entities by id before rendering to avoid flickering. + /// + /// Render is parallel by default, and if there's z-fighting, it may cause flickering. + /// Default fix for the issue is to set `depth_bias` per material. + /// When it is not possible, entities sorting can be used. + /// + /// This option costs performance and disabled by default. + pub stable_sort_z_fighting: bool, +} diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index e1ca40cf85..29d46ae926 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -6,6 +6,7 @@ extern crate core; pub mod batching; pub mod camera; pub mod color; +pub mod deterministic; pub mod extract_component; pub mod extract_instances; mod extract_param; @@ -48,6 +49,7 @@ use bevy_window::{PrimaryWindow, RawHandleWrapper}; use globals::GlobalsPlugin; use renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue}; +use crate::deterministic::DeterministicRenderingConfig; use crate::{ camera::CameraPlugin, mesh::{morph::MorphPlugin, Mesh, MeshPlugin}, @@ -216,6 +218,8 @@ pub const MATHS_SHADER_HANDLE: Handle = Handle::weak_from_u128(106653563 impl Plugin for RenderPlugin { /// Initializes the renderer, sets up the [`RenderSet`] and creates the rendering sub-app. fn build(&self, app: &mut App) { + app.init_resource::(); + app.init_asset::() .init_asset_loader::(); diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index a92d2fdeda..4427b408e1 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -12,6 +12,7 @@ use bevy_transform::{components::GlobalTransform, TransformSystem}; use std::cell::Cell; use thread_local::ThreadLocal; +use crate::deterministic::DeterministicRenderingConfig; use crate::{ camera::{ camera_system, Camera, CameraProjection, OrthographicProjection, PerspectiveProjection, @@ -392,6 +393,7 @@ pub fn check_visibility( &GlobalTransform, Has, )>, + deterministic_rendering_config: Res, ) { for (mut visible_entities, frustum, maybe_view_mask, camera) in &mut view_query { if !camera.is_active { @@ -452,6 +454,11 @@ pub fn check_visibility( for cell in &mut thread_queues { visible_entities.entities.append(cell.get_mut()); } + if deterministic_rendering_config.stable_sort_z_fighting { + // We can use the faster unstable sort here because + // the values (`Entity`) are guaranteed to be unique. + visible_entities.entities.sort_unstable(); + } } } diff --git a/examples/3d/deterministic.rs b/examples/3d/deterministic.rs new file mode 100644 index 0000000000..5d8f22f2ce --- /dev/null +++ b/examples/3d/deterministic.rs @@ -0,0 +1,87 @@ +//! Shows how to enable deterministic rendering which helps with flickering due to z-fighting. +//! Rendering is not deterministic by default. +//! Note most users don't need rendering to be deterministic, and should rely on depth bias instead. + +use bevy::app::App; +use bevy::app::Startup; +use bevy::prelude::shape::Plane; +use bevy::prelude::*; +use bevy::render::deterministic::DeterministicRenderingConfig; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, (keys, update_help).chain()) + .run(); +} + +fn setup( + mut commands: Commands, + mut materials: ResMut>, + mut meshes: ResMut>, + mut deterministic_rendering_config: ResMut, +) { + // Safe default. + deterministic_rendering_config.stable_sort_z_fighting = true; + + // Help message will be rendered there. + commands.spawn(TextBundle::default()); + + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(3.0, 3.0, 3.0).looking_at(Vec3::new(0., 0., 0.), Vec3::Y), + ..default() + }); + + let mesh = meshes.add(Plane::from_size(2.0)); + for i in 0..360 { + let color = Color::hsl(i as f32, 1.0, 0.5); + commands.spawn(PbrBundle { + mesh: mesh.clone(), + material: materials.add(StandardMaterial { + base_color: color, + // Setting depth bias would be a default choice to fix z-fighting. + // When it is not possible, deterministic rendering can be used. + // Here we intentionally don't use depth bias to demonstrate the issue. + depth_bias: 0.0, + unlit: true, + ..Default::default() + }), + ..default() + }); + } +} + +fn keys( + mut deterministic_rendering_config: ResMut, + keyboard_input: Res>, +) { + if keyboard_input.just_pressed(KeyCode::KeyD) { + deterministic_rendering_config.stable_sort_z_fighting ^= true; + } +} + +fn update_help( + mut text: Query<&mut Text>, + deterministic_rendering_config: Res, +) { + if deterministic_rendering_config.is_changed() { + *text.single_mut() = Text::from_section( + format!( + "\ + Press D to enable/disable deterministic rendering\n\ + \n\ + Deterministic rendering: {}\n\ + \n\ + When rendering is not deterministic, you may notice flickering due to z-fighting\n\ + \n\ + Warning: may cause seizures for people with photosensitive epilepsy", + deterministic_rendering_config.stable_sort_z_fighting + ), + TextStyle { + font_size: 20., + ..default() + }, + ); + } +} diff --git a/examples/README.md b/examples/README.md index ca47cf4647..4eac95dc55 100644 --- a/examples/README.md +++ b/examples/README.md @@ -123,6 +123,7 @@ Example | Description [Atmospheric Fog](../examples/3d/atmospheric_fog.rs) | A scene showcasing the atmospheric fog effect [Blend Modes](../examples/3d/blend_modes.rs) | Showcases different blend modes [Deferred Rendering](../examples/3d/deferred_rendering.rs) | Renders meshes with both forward and deferred pipelines +[Deterministic rendering](../examples/3d/deterministic.rs) | Stop flickering from z-fighting at a performance cost [Fog](../examples/3d/fog.rs) | A scene showcasing the distance fog effect [Generate Custom Mesh](../examples/3d/generate_custom_mesh.rs) | Simple showcase of how to generate a custom mesh with a custom texture [Lighting](../examples/3d/lighting.rs) | Illustrates various lighting options in a simple scene