diff --git a/Cargo.toml b/Cargo.toml index d531177230f40f..a7fa08aa34cd7b 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 00000000000000..224d9159ff7eec --- /dev/null +++ b/crates/bevy_render/src/deterministic.rs @@ -0,0 +1,14 @@ +use bevy_ecs::system::Resource; + +/// Configure deterministic rendering to deal with flickering due to z-fighting and parallel rendering. +#[derive(Resource, Default)] +pub struct DeterministicRenderingConfig { + /// Sort visible entities by id before rendering to avoid flickering. + /// + /// Render is parallel by default, and in certain situations it may cause visual artifacts. + /// 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 e7ed0545eebd14..dd585811cbde53 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}, @@ -209,6 +211,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 a92d2fdeda0fd8..7474518efa9c8e 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,9 @@ 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 { + visible_entities.entities.sort_unstable(); + } } } diff --git a/examples/3d/deterministic.rs b/examples/3d/deterministic.rs new file mode 100644 index 00000000000000..9509131a5c99a8 --- /dev/null +++ b/examples/3d/deterministic.rs @@ -0,0 +1,86 @@ +//! Shows how to enable deterministic rendering. 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 to avoid epileptic seizures. + 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).into()); + 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 observe 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 7a0e2fa88dd836..a83b73f9ccc0c9 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