depth_of_field/
depth_of_field.rs1use bevy::{
13 camera::PhysicalCameraParameters,
14 core_pipeline::tonemapping::Tonemapping,
15 gltf::GltfMeshName,
16 pbr::Lightmap,
17 post_process::{
18 bloom::Bloom,
19 dof::{self, DepthOfField, DepthOfFieldMode},
20 },
21 prelude::*,
22};
23
24const FOCAL_DISTANCE_SPEED: f32 = 0.05;
27const APERTURE_F_STOP_SPEED: f32 = 0.01;
29
30const MIN_FOCAL_DISTANCE: f32 = 0.01;
32const MIN_APERTURE_F_STOPS: f32 = 0.05;
34
35#[derive(Clone, Copy, Resource)]
37struct AppSettings {
38 focal_distance: f32,
40
41 aperture_f_stops: f32,
46
47 mode: Option<DepthOfFieldMode>,
50}
51
52fn main() {
53 App::new()
54 .init_resource::<AppSettings>()
55 .add_plugins(DefaultPlugins.set(WindowPlugin {
56 primary_window: Some(Window {
57 title: "Bevy Depth of Field Example".to_string(),
58 ..default()
59 }),
60 ..default()
61 }))
62 .add_systems(Startup, setup)
63 .add_systems(Update, tweak_scene)
64 .add_systems(
65 Update,
66 (adjust_focus, change_mode, update_dof_settings, update_text).chain(),
67 )
68 .run();
69}
70
71fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_settings: Res<AppSettings>) {
72 let mut camera = commands.spawn((
75 Camera3d::default(),
76 Transform::from_xyz(0.0, 4.5, 8.25).looking_at(Vec3::ZERO, Vec3::Y),
77 Tonemapping::TonyMcMapface,
78 Bloom::NATURAL,
79 ));
80
81 if let Some(depth_of_field) = Option::<DepthOfField>::from(*app_settings) {
83 camera.insert(depth_of_field);
84 }
85
86 commands.spawn(SceneRoot(asset_server.load(
88 GltfAssetLabel::Scene(0).from_asset("models/DepthOfFieldExample/DepthOfFieldExample.glb"),
89 )));
90
91 commands.spawn((
93 create_text(&app_settings),
94 Node {
95 position_type: PositionType::Absolute,
96 bottom: px(12),
97 left: px(12),
98 ..default()
99 },
100 ));
101}
102
103fn adjust_focus(input: Res<ButtonInput<KeyCode>>, mut app_settings: ResMut<AppSettings>) {
105 let distance_delta = if input.pressed(KeyCode::ArrowDown) {
107 -FOCAL_DISTANCE_SPEED
108 } else if input.pressed(KeyCode::ArrowUp) {
109 FOCAL_DISTANCE_SPEED
110 } else {
111 0.0
112 };
113
114 let f_stop_delta = if input.pressed(KeyCode::ArrowLeft) {
116 -APERTURE_F_STOP_SPEED
117 } else if input.pressed(KeyCode::ArrowRight) {
118 APERTURE_F_STOP_SPEED
119 } else {
120 0.0
121 };
122
123 app_settings.focal_distance =
124 (app_settings.focal_distance + distance_delta).max(MIN_FOCAL_DISTANCE);
125 app_settings.aperture_f_stops =
126 (app_settings.aperture_f_stops + f_stop_delta).max(MIN_APERTURE_F_STOPS);
127}
128
129fn change_mode(input: Res<ButtonInput<KeyCode>>, mut app_settings: ResMut<AppSettings>) {
131 if !input.just_pressed(KeyCode::Space) {
132 return;
133 }
134
135 app_settings.mode = match app_settings.mode {
136 Some(DepthOfFieldMode::Bokeh) => Some(DepthOfFieldMode::Gaussian),
137 Some(DepthOfFieldMode::Gaussian) => None,
138 None => Some(DepthOfFieldMode::Bokeh),
139 }
140}
141
142impl Default for AppSettings {
143 fn default() -> Self {
144 Self {
145 focal_distance: 7.0,
147
148 aperture_f_stops: 1.0 / 8.0,
153
154 mode: Some(DepthOfFieldMode::Bokeh),
156 }
157 }
158}
159
160fn update_dof_settings(
162 mut commands: Commands,
163 view_targets: Query<Entity, With<Camera>>,
164 app_settings: Res<AppSettings>,
165) {
166 let depth_of_field: Option<DepthOfField> = (*app_settings).into();
167 for view in view_targets.iter() {
168 match depth_of_field {
169 None => {
170 commands.entity(view).remove::<DepthOfField>();
171 }
172 Some(depth_of_field) => {
173 commands.entity(view).insert(depth_of_field);
174 }
175 }
176 }
177}
178
179fn tweak_scene(
181 mut commands: Commands,
182 asset_server: Res<AssetServer>,
183 mut materials: ResMut<Assets<StandardMaterial>>,
184 mut lights: Query<&mut DirectionalLight, Changed<DirectionalLight>>,
185 mut named_entities: Query<
186 (Entity, &GltfMeshName, &MeshMaterial3d<StandardMaterial>),
187 (With<Mesh3d>, Without<Lightmap>),
188 >,
189) {
190 for mut light in lights.iter_mut() {
192 light.shadows_enabled = true;
193 }
194
195 for (entity, name, material) in named_entities.iter_mut() {
197 if &**name == "CircuitBoard" {
198 materials.get_mut(material).unwrap().lightmap_exposure = 10000.0;
199 commands.entity(entity).insert(Lightmap {
200 image: asset_server.load("models/DepthOfFieldExample/CircuitBoardLightmap.hdr"),
201 ..default()
202 });
203 }
204 }
205}
206
207fn update_text(mut texts: Query<&mut Text>, app_settings: Res<AppSettings>) {
209 for mut text in texts.iter_mut() {
210 *text = create_text(&app_settings);
211 }
212}
213
214fn create_text(app_settings: &AppSettings) -> Text {
216 app_settings.help_text().into()
217}
218
219impl From<AppSettings> for Option<DepthOfField> {
220 fn from(app_settings: AppSettings) -> Self {
221 app_settings.mode.map(|mode| DepthOfField {
222 mode,
223 focal_distance: app_settings.focal_distance,
224 aperture_f_stops: app_settings.aperture_f_stops,
225 max_depth: 14.0,
226 ..default()
227 })
228 }
229}
230
231impl AppSettings {
232 fn help_text(&self) -> String {
234 let Some(mode) = self.mode else {
235 return "Mode: Off (Press Space to change)".to_owned();
236 };
237
238 let sensor_height = PhysicalCameraParameters::default().sensor_height;
242 let fov = PerspectiveProjection::default().fov;
243
244 format!(
245 "Focal distance: {:.2} m (Press Up/Down to change)
246Aperture F-stops: f/{:.2} (Press Left/Right to change)
247Sensor height: {:.2}mm
248Focal length: {:.2}mm
249Mode: {} (Press Space to change)",
250 self.focal_distance,
251 self.aperture_f_stops,
252 sensor_height * 1000.0,
253 dof::calculate_focal_length(sensor_height, fov) * 1000.0,
254 match mode {
255 DepthOfFieldMode::Bokeh => "Bokeh",
256 DepthOfFieldMode::Gaussian => "Gaussian",
257 }
258 )
259 }
260}