Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Z-fighting is not stable and causes flickering #10243

Open
stepancheg opened this issue Oct 23, 2023 · 8 comments
Open

Z-fighting is not stable and causes flickering #10243

stepancheg opened this issue Oct 23, 2023 · 8 comments
Labels
A-Rendering Drawing game state to the screen C-Bug An unexpected or incorrect behavior S-Needs-Design This issue requires design work to think about how it would best be accomplished X-Controversial There is active debate or serious implications around merging this PR

Comments

@stepancheg
Copy link
Contributor

stepancheg commented Oct 23, 2023

Bevy version

0.11.3

Relevant system information

AdapterInfo { name: "Apple M1 Pro", vendor: 0, device: 0, device_type: IntegratedGpu, driver: "", driver_info: "", backend: Metal }

What you did

Add three planes of different colors at exactly the same location.

use bevy::app::App;
use bevy::app::Startup;
use bevy::prelude::shape::Plane;
use bevy::prelude::*;

fn setup(
    mut commands: Commands,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut meshes: ResMut<Assets<Mesh>>,
) {
    commands.spawn(Camera3dBundle {
        transform: Transform::from_xyz(3.0, 3.0, 3.0).looking_at(Vec3::new(0., 0., 0.), Vec3::Y),
        ..default()
    });

    let red = materials.add(Color::RED.into());
    let green = materials.add(Color::GREEN.into());
    let blue = materials.add(Color::BLUE.into());

    for color in [red, green, blue] {
        // Intentionally spawn 3 planes in the same place.
        commands.spawn(PbrBundle {
            mesh: meshes.add(Plane::from_size(2.0).into()),
            material: color,
            ..default()
        });
    }
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .run();
}

Compiled with -O1.

What went wrong

Rectangle is constantly changing color.

I suspect that's because order of rendering is not deterministic.

If it not always easy to make sure that triangles never occupy the same place, so it would be convenient if Bevy rendered these meshes in some deterministic order, regardless of what's that order is. At least if entities did not change.

Attaching screen recording.

Screen.Recording.2023-10-23.at.23.28.51.mov
@stepancheg stepancheg added C-Bug An unexpected or incorrect behavior S-Needs-Triage This issue needs to be labelled labels Oct 23, 2023
@alice-i-cecile alice-i-cecile added A-Rendering Drawing game state to the screen and removed S-Needs-Triage This issue needs to be labelled labels Oct 23, 2023
@alice-i-cecile
Copy link
Member

alice-i-cecile commented Oct 23, 2023

This is an extremely common effect known as "z-fighting". I'm not sure if this problem can or should be automatically be solved: presumably there's a performance cost to doing so?

@alice-i-cecile alice-i-cecile added S-Needs-Design This issue requires design work to think about how it would best be accomplished X-Controversial There is active debate or serious implications around merging this PR labels Oct 23, 2023
@alice-i-cecile alice-i-cecile changed the title Non-deterministic render ordering results in blinking on screen Z-fighting Oct 23, 2023
@alice-i-cecile alice-i-cecile changed the title Z-fighting Z-fighting is not stable and causes flickering Oct 23, 2023
@alice-i-cecile
Copy link
Member

[7:37 PM]pcwalton: I feel like the bug here is not z-fighting per se but rather that our rendering order isn't deterministic
[7:37 PM]robswain: it's because of EntityHashMap i guess
[7:37 PM]robswain: right
[7:38 PM]robswain: the phase needs to also sort by entity index as a tie breaker
[7:38 PM]robswain: then it should be stable

Disussed on Discord by @pcwalton and @superdump.

@robtfm
Copy link
Contributor

robtfm commented Oct 24, 2023

you can add a depth bias to control the order:

let red = materials.add(StandardMaterial {
    depth_bias: 1.0,
    ..Color::RED.into()
});

reference

@stepancheg
Copy link
Contributor Author

@robtfm TIL, thanks!

This partially solves the issue with simple colors (I can just assign random bias to colors), but I believe the issue still exists when textures are used (two planes with the same texture in the same "plane", but one is slightly shifted so planes intersect).

@robtfm
Copy link
Contributor

robtfm commented Oct 24, 2023

It should work for texture materials too

@stepancheg
Copy link
Contributor Author

@robtfm I don't understand.

Users cannot assign different depth_bias to different pixels of texture. So if two planes share the same material, they have the same depth_bias. And I think users are not supposed to create a new material instance per each new mesh.

@robtfm
Copy link
Contributor

robtfm commented Oct 24, 2023

If you want to separate two instances you have to give them some different information. So you can either give them slightly different translation depths with respect to the camera, or you can create a different material with a different depth bias for each instance - note that doesn’t mean making a separate texture, so the impact isn’t that bad (and will be close to nothing with material batching for reasonable numbers of entities).

Adding a stable entity-based render order would also work to disambiguate but you lose control over the order unless you also carefully manage your entity creation, which sounds undesirable to me, and adding more sorting won’t be free either.

@stepancheg
Copy link
Contributor Author

#11248

github-merge-queue bot pushed a commit that referenced this issue Jan 9, 2024
# 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Bug An unexpected or incorrect behavior S-Needs-Design This issue requires design work to think about how it would best be accomplished X-Controversial There is active debate or serious implications around merging this PR
Projects
None yet
Development

No branches or pull requests

3 participants