-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Exposure settings #8407
Exposure settings #8407
Changes from 2 commits
432c427
8fa78a9
cbeed57
0cfbfd7
ef62d30
f5c4166
ce69ab2
e144815
56bb844
4dd2ede
64bcfa8
f9e0730
c42824e
71860bb
7dc7966
f78a0af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,6 +76,36 @@ pub struct ComputedCameraValues { | |
old_viewport_size: Option<UVec2>, | ||
} | ||
|
||
#[derive(Component)] | ||
pub struct ExposureSettings { | ||
pub aperture_f_stops: f32, | ||
pub shutter_speed_s: f32, | ||
pub sensitivity_iso: f32, | ||
} | ||
|
||
impl Default for ExposureSettings { | ||
fn default() -> Self { | ||
Self { | ||
aperture_f_stops: 4.0, | ||
shutter_speed_s: 1.0 / 250.0, | ||
sensitivity_iso: 100.0, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd suggest ditching these individual settings and just passing the ev100 value directly. There's lots of sources online that provide tables of ev100 values, for instance this one from wikipedia There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, might be worth providing constants with a couple defaults. For instance, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed! You could also provide an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, unless we had other effects of these parameters they should really be collapsed down into one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea @JMS55 - I was thinking that it would be good to expose (HA!!!) the functionality to provide the parameters for those who it is more familiar. Was first thinking of an enum, but I agree the constructor approach could be nicer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alice-i-cecile hmm... now that you mention it... the filament docs did talk about how the different parameters affect depth of field (or focal depth - the aperture setting I think, I'm not familiar with cameras beyond physics stuff) and motion blur (shutter speed). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's correct :) At the same time, I don't think we want those baked into our rendering engine by default: motion blur and focal depth are very specific effects that should be used artistically. We're not simulating a real camera IMO, so we should just expose Ev100 and then provide independent control for things like motion blur and focal depth when we have them. |
||
} | ||
} | ||
} | ||
|
||
impl ExposureSettings { | ||
#[inline] | ||
pub fn ev100(&self) -> f32 { | ||
(self.aperture_f_stops * self.aperture_f_stops / self.shutter_speed_s).log2() | ||
- (self.sensitivity_iso / 100.0).log2() | ||
} | ||
|
||
#[inline] | ||
pub fn exposure(&self) -> f32 { | ||
1.0 / (2.0f32.powf(self.ev100()) * 1.2) | ||
} | ||
} | ||
|
||
/// The defining component for camera entities, storing information about how and what to render | ||
/// through this camera. | ||
/// | ||
|
@@ -573,6 +603,7 @@ pub struct ExtractedCamera { | |
pub output_mode: CameraOutputMode, | ||
pub msaa_writeback: bool, | ||
pub sorted_camera_index_for_target: usize, | ||
pub exposure: f32, | ||
} | ||
|
||
pub fn extract_cameras( | ||
|
@@ -585,6 +616,7 @@ pub fn extract_cameras( | |
&GlobalTransform, | ||
&VisibleEntities, | ||
Option<&ColorGrading>, | ||
Option<&ExposureSettings>, | ||
Option<&TemporalJitter>, | ||
)>, | ||
>, | ||
|
@@ -598,6 +630,7 @@ pub fn extract_cameras( | |
transform, | ||
visible_entities, | ||
color_grading, | ||
exposure_settings, | ||
temporal_jitter, | ||
) in query.iter() | ||
{ | ||
|
@@ -630,6 +663,9 @@ pub fn extract_cameras( | |
msaa_writeback: camera.msaa_writeback, | ||
// this will be set in sort_cameras | ||
sorted_camera_index_for_target: 0, | ||
exposure: exposure_settings | ||
.map(|e| e.exposure()) | ||
.unwrap_or_else(|| ExposureSettings::default().exposure()), | ||
}, | ||
ExtractedView { | ||
projection: camera.projection_matrix(), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,13 +3,13 @@ | |
|
||
use std::f32::consts::PI; | ||
|
||
use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*}; | ||
use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*, render::camera::ExposureSettings}; | ||
|
||
fn main() { | ||
App::new() | ||
.add_plugins(DefaultPlugins) | ||
.add_systems(Startup, setup) | ||
.add_systems(Update, (movement, animate_light_direction)) | ||
.add_systems(Update, (update_exposure, movement, animate_light_direction)) | ||
.run(); | ||
} | ||
|
||
|
@@ -207,6 +207,7 @@ fn setup( | |
// directional 'sun' light | ||
commands.spawn(DirectionalLightBundle { | ||
directional_light: DirectionalLight { | ||
illuminance: 1000.0, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The sun has an illuminance of ~100,000 lux. Though, having that bright a directional light that bright is of course going to overpower all the other lights in the scene (even stuff in shadows will mostly be lit by bounce lighting and light from the sky) |
||
shadows_enabled: true, | ||
..default() | ||
}, | ||
|
@@ -227,11 +228,101 @@ fn setup( | |
..default() | ||
}); | ||
|
||
let exposure_settings = ExposureSettings::default(); | ||
let style = TextStyle { | ||
font: asset_server.load("fonts/FiraMono-Medium.ttf"), | ||
font_size: 18.0, | ||
color: Color::WHITE, | ||
}; | ||
|
||
commands.spawn( | ||
TextBundle::from_sections(vec![ | ||
TextSection::new( | ||
format!("Aperture: f/{:.0}\n", exposure_settings.aperture_f_stops), | ||
style.clone(), | ||
), | ||
TextSection::new( | ||
format!( | ||
"Shutter speed: 1/{:.0}s\n", | ||
1.0 / exposure_settings.shutter_speed_s | ||
), | ||
style.clone(), | ||
), | ||
TextSection::new( | ||
format!( | ||
"Sensitivity: ISO {:.0}\n", | ||
exposure_settings.sensitivity_iso | ||
), | ||
style.clone(), | ||
), | ||
TextSection::new("\n\n", style.clone()), | ||
TextSection::new("Controls\n", style.clone()), | ||
TextSection::new("---------------\n", style.clone()), | ||
TextSection::new("1/2 - Decrease/Increase aperture\n", style.clone()), | ||
TextSection::new("3/4 - Decrease/Increase shutter speed\n", style.clone()), | ||
TextSection::new("5/6 - Decrease/Increase sensitivity\n", style), | ||
]) | ||
.with_style(Style { | ||
position_type: PositionType::Absolute, | ||
top: Val::Px(10.0), | ||
left: Val::Px(10.0), | ||
..default() | ||
}), | ||
); | ||
|
||
// camera | ||
commands.spawn(Camera3dBundle { | ||
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), | ||
..default() | ||
}); | ||
commands.spawn(( | ||
Camera3dBundle { | ||
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), | ||
..default() | ||
}, | ||
exposure_settings, | ||
)); | ||
} | ||
|
||
fn update_exposure( | ||
key_input: Res<Input<KeyCode>>, | ||
mut query: Query<&mut ExposureSettings>, | ||
mut text: Query<&mut Text>, | ||
) { | ||
let mut text = text.single_mut(); | ||
let mut exposure_settings = query.single_mut(); | ||
if key_input.just_pressed(KeyCode::Key2) { | ||
exposure_settings.aperture_f_stops *= 2.0; | ||
text.sections[0].value = format!("Aperture: f/{:.0}\n", exposure_settings.aperture_f_stops); | ||
} else if key_input.just_pressed(KeyCode::Key1) { | ||
exposure_settings.aperture_f_stops *= 0.5; | ||
text.sections[0].value = format!("Aperture: f/{:.0}\n", exposure_settings.aperture_f_stops); | ||
} | ||
if key_input.just_pressed(KeyCode::Key4) { | ||
exposure_settings.shutter_speed_s *= 2.0; | ||
text.sections[1].value = format!( | ||
"Shutter speed: 1/{:.0}s\n", | ||
1.0 / exposure_settings.shutter_speed_s | ||
); | ||
} else if key_input.just_pressed(KeyCode::Key3) { | ||
exposure_settings.shutter_speed_s *= 0.5; | ||
text.sections[1].value = format!( | ||
"Shutter speed: 1/{:.0}s\n", | ||
1.0 / exposure_settings.shutter_speed_s | ||
); | ||
} | ||
if key_input.just_pressed(KeyCode::Key6) { | ||
exposure_settings.sensitivity_iso += 100.0; | ||
text.sections[2].value = format!( | ||
"Sensitivity: ISO {:.0}\n", | ||
exposure_settings.sensitivity_iso | ||
); | ||
} else if key_input.just_pressed(KeyCode::Key5) { | ||
exposure_settings.sensitivity_iso -= 100.0; | ||
text.sections[2].value = format!( | ||
"Sensitivity: ISO {:.0}\n", | ||
exposure_settings.sensitivity_iso | ||
); | ||
} | ||
if key_input.just_pressed(KeyCode::D) { | ||
*exposure_settings = ExposureSettings::default(); | ||
} | ||
} | ||
|
||
fn animate_light_direction( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assumes that indirect light is already in photometric units. There may need to be a scale factor to undo any pre-exposure that's baked into the contents of the cubemaps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. There was talk about an "intensity" field for environment map lighting. I decided to leave it out in my original PR because I was worried it would be abused. Maybe it's time for that now.