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