bevy icon indicating copy to clipboard operation
bevy copied to clipboard

Specular from area lights is disproportionately bright

Open DGriffin91 opened this issue 1 year ago • 2 comments

Bevy version Main https://github.com/bevyengine/bevy/commit/dcb8a13b223fbc9425f7af01b3941dd80a229384, 0.13

This isn't a new issue. Not sure how far it goes back, or if there's exists a previous version without it.

Specular reflections from spot/point area lights are disproportionately bright relative to the diffuse response. This often results in extremely bright specular reflections given sufficiently large area lights.

image This image also displays other specular-related issues in bevy that are not relevant to this issue, let's keep this issue focused, please refer to the blender comparison images below.

Test setup: I disabled Tonemapping in both blender and bevy. I visually matched the light brightness using just diffuse. The diffuse falloff in bevy doesn't exactly match blender, but it's close. In blender I'm rendering using Cycles with direct light only (no GI).

I apologize in advance for the amount of comparison images needed. Note that bevy 0 radius is very similar to blender 0 radius across the board, and bevy 1 radius diffuse is similar to blender 1 radius diffuse (though blender wraps around a bit more here). But bevy 1 radius specular is dramatically brighter than blender 1 radius specular.

Bevy 0 radius, diffuse only: bevy_0r_diff

Blender 0 radius, diffuse only: blender_0r_diff

Bevy 0 radius, specular only: bevy_0r_spec

Blender 0 radius, specular only: blender_0r_spec

Bevy 0 radius, both specular and diffuse: bevy_0r

Blender 0 radius, both specular and diffuse: blender_0r

Bevy 1 radius, diffuse only: bevy_1r_diff

Blender 1 radius, diffuse only: blender_1r_diff

Bevy 1 radius, specular only: bevy_1r_spec

Blender 1 radius, specular only: blender_1r_spec

Bevy 1 radius, both specular and diffuse: bevy_1r

Blender 1 radius, both specular and diffuse: blender_1r

Bevy 1 radius, both specular and diffuse, sharp: bevy_1r_sharp

Blender 1 radius, both specular and diffuse, sharp: blender_1r_sharp

Blender Test Scene (Blender 4.1): bevy_spec_ref.zip Bevy test scene (Bevy Main https://github.com/bevyengine/bevy/commit/dcb8a13b223fbc9425f7af01b3941dd80a229384):

fn main() {
    App::new()
        .insert_resource(ClearColor(Color::BLACK))
        .insert_resource(AmbientLight::NONE)
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, screenshot_on_spacebar)
        .run();
}
fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    let sphere_mesh = meshes.add(Sphere::new(1.0).mesh().uv(128, 64));
    let surface_material = materials.add(StandardMaterial {
        // Set RGB to 0 to disable diffuse, otherwise use 0.5
        base_color: Color::linear_rgba(0.5, 0.5, 0.5, 1.0),
        // Set reflectance to 0 to disable specular, otherwise use 0.5
        reflectance: 0.5,
        perceptual_roughness: 0.5, // use 0.089 or 0.5
        ..default()
    });
    commands.spawn(PbrBundle {
        mesh: meshes.add(Plane3d::default().mesh().size(100.0, 100.0)),
        material: surface_material.clone(),
        ..default()
    });
    commands.spawn(PbrBundle {
        transform: Transform::from_xyz(-2.5, 1.0, 3.0),
        mesh: sphere_mesh.clone(),
        material: surface_material.clone(),
        ..default()
    });
    commands.spawn(PbrBundle {
        transform: Transform::from_xyz(0.0, 2.5, 3.0),
        mesh: sphere_mesh.clone(),
        material: surface_material.clone(),
        ..default()
    });
    commands.spawn(PointLightBundle {
        point_light: PointLight {
            intensity: 80000.0,
            radius: 1.0,
            ..default()
        },
        transform: Transform::from_xyz(0.0, 1.0, 0.0),
        ..default()
    });
    commands.spawn(Camera3dBundle {
        projection: Projection::Perspective(PerspectiveProjection {
            fov: 45.2f32.to_radians(),
            ..default()
        }),
        tonemapping: Tonemapping::None,
        transform: Transform::from_xyz(0.0, 1.0, 7.0).looking_at(Vec3::ZERO, Vec3::Y),
        ..default()
    });
}
fn screenshot_on_spacebar(
    input: Res<ButtonInput<KeyCode>>,
    main_window: Query<Entity, With<PrimaryWindow>>,
    mut screenshot_manager: ResMut<ScreenshotManager>,
    mut counter: Local<u32>,
) {
    if input.just_pressed(KeyCode::Space) {
        let path = format!("./screenshot-{}.png", *counter);
        *counter += 1;
        screenshot_manager
            .save_screenshot_to_disk(main_window.single(), path)
            .unwrap();
    }
}

DGriffin91 avatar May 11 '24 01:05 DGriffin91

You mentioned a "Soft Falloff" Settung in Blender in #13318. Is it enabled in the comparison images?

geckoxx avatar May 18 '24 08:05 geckoxx

@geckoxx It is not.

DGriffin91 avatar May 18 '24 08:05 DGriffin91

In blender I'm rendering using Cycles with direct light only (no GI).

Shouldn't we be using Eevee as our "baseline blender comparison", given that Cycles is a raytracer whereas Eevee is structured more like a game engine renderer?

Not a huge deal, as I know Cycles and Eevee are calibrated to each other, but it still seems like Eevee should probably be the point of comparison so its more apples to apples?

cart avatar Jun 06 '24 20:06 cart

I used cycles since I figured it would be more accurate and a better "reference" renderer. And eevee makes different tradeoffs than bevy does so comparing to a more accurate renderer that's trying to be generally unbiased is valuable. Valid point though about eevee being more similar to a game engine. I partly didn't want to have even more screenshots.

Blender EEVEE 0 radius, diffuse only: Blender EEVEE 0 radius, diffuse only

Blender EEVEE 0 radius, specular only: Blender EEVEE 0 radius, specular only

Blender EEVEE 0 radius, both specular and diffuse: Blender EEVEE 0 radius, both specular and diffuse

Blender EEVEE 1 radius, diffuse only: Blender EEVEE 1 radius, diffuse only

Blender EEVEE 1 radius, specular only: Blender EEVEE 1 radius, specular only

Blender EEVEE 1 radius, both specular and diffuse: Blender EEVEE 1 radius, both specular and diffuse

Blender EEVEE 1 radius, both specular and diffuse, sharp: Blender EEVEE 1 radius, both specular and diffuse, sharp

DGriffin91 avatar Jun 06 '24 23:06 DGriffin91

I used cycles since I figured it would be more accurate and a better "reference" renderer. And eevee makes different tradeoffs than bevy does so comparing to a more accurate renderer that's trying to be generally unbiased is valuable. Valid point though about eevee being more similar to a game engine. I partly didn't want to have even more screenshots.

Yeah this is a reasonable take. Both seem valuable, but when it comes to calibrating values, I think it makes the most sense to target Eevee, as in theory the way light behaves is more aligned. And when people are designing their scenes in Blender, we have the most hope (and therefore the most value) in having parity with Eevee. We can't really hope to synchronize perfectly with Cycles. And calibrating to Cycles introduces the risk of not syncing correctly with Eevee.

In short: I think we should focus our time on Eevee calibration, except for areas where techniques significantly diverge, or in cases where we're introducing raytracing techniques that are closer to Cycles than whatever Eevee is doing.

cart avatar Jun 06 '24 23:06 cart

Thanks for the Eevee shots! It does appear that we're way out of sync with both renderers :)

cart avatar Jun 06 '24 23:06 cart

Worth noting that Eevee is a bit more of a moving target. In the next version of blender it will also use ray tracing. Cycles is inherently less of a moving target because it's trying to be "unbiased".

Our BRDF implementation is much more similar to the one cycles uses. I don't really think we should chase blender in particular, it's just one dcc and certainly not the most popular in the industry. Blender also has bugs and isn't always correct. But cycles is more likely to be correct than Eevee. Ultimately when making comparisons and working on Bevy's renderer, it takes actually understanding the BRDF/BSDF and how the lighting should really work. This comparison specifically sets up cycles to narrow in on the BRDF. It would obviously be easy to accidentally use cycle's GI and be comparing apples to bananas (this is partly why I included the blend file, so others could play with it). Using Eevee or cycles as a reference here is just an easy way point out that we're way off.

To see one aspect of how our BRDF impl is more similar to Cycles, compare the "0 radius, specular only":

Bevy 0 radius, specular only: Bevy 0 radius, specular only

Blender 0 radius, specular only: Blender 0 radius, specular only

Blender EEVEE 0 radius, specular only Blender EEVEE 0 radius, specular only

DGriffin91 avatar Jun 07 '24 00:06 DGriffin91

Hmmm solid points. I guess the takeaway here is "find the reference that is appropriate for the algorithm being tested", which is sadly harder than "always use Eevee as a reference" or "always use Cycles as a reference".

cart avatar Jun 21 '24 18:06 cart