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

<img width="1392" alt="image"
src="https://github.com/bevyengine/bevy/assets/28969/c735bce1-3a71-44cd-8677-c19f6c0ee6bd">

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
Stepan Koltsov 2024-01-09 00:46:01 +00:00 committed by GitHub
parent 9c972f037e
commit 06bf928927
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 124 additions and 0 deletions

View file

@ -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"

View file

@ -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,
}

View file

@ -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<Shader> = 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::<DeterministicRenderingConfig>();
app.init_asset::<Shader>()
.init_asset_loader::<ShaderLoader>();

View file

@ -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<NoFrustumCulling>,
)>,
deterministic_rendering_config: Res<DeterministicRenderingConfig>,
) {
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();
}
}
}

View file

@ -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<Assets<StandardMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
mut deterministic_rendering_config: ResMut<DeterministicRenderingConfig>,
) {
// 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<DeterministicRenderingConfig>,
keyboard_input: Res<ButtonInput<KeyCode>>,
) {
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<DeterministicRenderingConfig>,
) {
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()
},
);
}
}

View file

@ -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