deferred_rendering/
deferred_rendering.rs

1//! This example compares Forward, Forward + Prepass, and Deferred rendering.
2
3use std::f32::consts::*;
4
5use bevy::{
6    core_pipeline::{
7        fxaa::Fxaa,
8        prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
9    },
10    image::ImageLoaderSettings,
11    math::ops,
12    pbr::{
13        CascadeShadowConfigBuilder, DefaultOpaqueRendererMethod, DirectionalLightShadowMap,
14        NotShadowCaster, NotShadowReceiver, OpaqueRendererMethod,
15    },
16    prelude::*,
17};
18
19fn main() {
20    App::new()
21        .insert_resource(DefaultOpaqueRendererMethod::deferred())
22        .insert_resource(DirectionalLightShadowMap { size: 4096 })
23        .add_plugins(DefaultPlugins)
24        .insert_resource(Pause(true))
25        .add_systems(Startup, (setup, setup_parallax))
26        .add_systems(Update, (animate_light_direction, switch_mode, spin))
27        .run();
28}
29
30fn setup(
31    mut commands: Commands,
32    asset_server: Res<AssetServer>,
33    mut materials: ResMut<Assets<StandardMaterial>>,
34    mut meshes: ResMut<Assets<Mesh>>,
35) {
36    commands.spawn((
37        Camera3d::default(),
38        Camera {
39            // Deferred both supports both hdr: true and hdr: false
40            hdr: false,
41            ..default()
42        },
43        Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
44        // MSAA needs to be off for Deferred rendering
45        Msaa::Off,
46        DistanceFog {
47            color: Color::srgb_u8(43, 44, 47),
48            falloff: FogFalloff::Linear {
49                start: 1.0,
50                end: 8.0,
51            },
52            ..default()
53        },
54        EnvironmentMapLight {
55            diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
56            specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
57            intensity: 2000.0,
58            ..default()
59        },
60        DepthPrepass,
61        MotionVectorPrepass,
62        DeferredPrepass,
63        Fxaa::default(),
64    ));
65
66    commands.spawn((
67        DirectionalLight {
68            illuminance: 15_000.,
69            shadows_enabled: true,
70            ..default()
71        },
72        CascadeShadowConfigBuilder {
73            num_cascades: 3,
74            maximum_distance: 10.0,
75            ..default()
76        }
77        .build(),
78        Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 0.0, -FRAC_PI_4)),
79    ));
80
81    // FlightHelmet
82    let helmet_scene = asset_server
83        .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
84
85    commands.spawn(SceneRoot(helmet_scene.clone()));
86    commands.spawn((
87        SceneRoot(helmet_scene),
88        Transform::from_xyz(-4.0, 0.0, -3.0),
89    ));
90
91    let mut forward_mat: StandardMaterial = Color::srgb(0.1, 0.2, 0.1).into();
92    forward_mat.opaque_render_method = OpaqueRendererMethod::Forward;
93    let forward_mat_h = materials.add(forward_mat);
94
95    // Plane
96    commands.spawn((
97        Mesh3d(meshes.add(Plane3d::default().mesh().size(50.0, 50.0))),
98        MeshMaterial3d(forward_mat_h.clone()),
99    ));
100
101    let cube_h = meshes.add(Cuboid::new(0.1, 0.1, 0.1));
102    let sphere_h = meshes.add(Sphere::new(0.125).mesh().uv(32, 18));
103
104    // Cubes
105    commands.spawn((
106        Mesh3d(cube_h.clone()),
107        MeshMaterial3d(forward_mat_h.clone()),
108        Transform::from_xyz(-0.3, 0.5, -0.2),
109    ));
110    commands.spawn((
111        Mesh3d(cube_h),
112        MeshMaterial3d(forward_mat_h),
113        Transform::from_xyz(0.2, 0.5, 0.2),
114    ));
115
116    let sphere_color = Color::srgb(10.0, 4.0, 1.0);
117    let sphere_pos = Transform::from_xyz(0.4, 0.5, -0.8);
118    // Emissive sphere
119    let mut unlit_mat: StandardMaterial = sphere_color.into();
120    unlit_mat.unlit = true;
121    commands.spawn((
122        Mesh3d(sphere_h.clone()),
123        MeshMaterial3d(materials.add(unlit_mat)),
124        sphere_pos,
125        NotShadowCaster,
126    ));
127    // Light
128    commands.spawn((
129        PointLight {
130            intensity: 800.0,
131            radius: 0.125,
132            shadows_enabled: true,
133            color: sphere_color,
134            ..default()
135        },
136        sphere_pos,
137    ));
138
139    // Spheres
140    for i in 0..6 {
141        let j = i % 3;
142        let s_val = if i < 3 { 0.0 } else { 0.2 };
143        let material = if j == 0 {
144            materials.add(StandardMaterial {
145                base_color: Color::srgb(s_val, s_val, 1.0),
146                perceptual_roughness: 0.089,
147                metallic: 0.0,
148                ..default()
149            })
150        } else if j == 1 {
151            materials.add(StandardMaterial {
152                base_color: Color::srgb(s_val, 1.0, s_val),
153                perceptual_roughness: 0.089,
154                metallic: 0.0,
155                ..default()
156            })
157        } else {
158            materials.add(StandardMaterial {
159                base_color: Color::srgb(1.0, s_val, s_val),
160                perceptual_roughness: 0.089,
161                metallic: 0.0,
162                ..default()
163            })
164        };
165        commands.spawn((
166            Mesh3d(sphere_h.clone()),
167            MeshMaterial3d(material),
168            Transform::from_xyz(
169                j as f32 * 0.25 + if i < 3 { -0.15 } else { 0.15 } - 0.4,
170                0.125,
171                -j as f32 * 0.25 + if i < 3 { -0.15 } else { 0.15 } + 0.4,
172            ),
173        ));
174    }
175
176    // sky
177    commands.spawn((
178        Mesh3d(meshes.add(Cuboid::new(2.0, 1.0, 1.0))),
179        MeshMaterial3d(materials.add(StandardMaterial {
180            base_color: Srgba::hex("888888").unwrap().into(),
181            unlit: true,
182            cull_mode: None,
183            ..default()
184        })),
185        Transform::from_scale(Vec3::splat(1_000_000.0)),
186        NotShadowCaster,
187        NotShadowReceiver,
188    ));
189
190    // Example instructions
191    commands.spawn((
192        Text::default(),
193        Node {
194            position_type: PositionType::Absolute,
195            top: Val::Px(12.0),
196            left: Val::Px(12.0),
197            ..default()
198        },
199    ));
200}
201
202#[derive(Resource)]
203struct Pause(bool);
204
205fn animate_light_direction(
206    time: Res<Time>,
207    mut query: Query<&mut Transform, With<DirectionalLight>>,
208    pause: Res<Pause>,
209) {
210    if pause.0 {
211        return;
212    }
213    for mut transform in &mut query {
214        transform.rotate_y(time.delta_secs() * PI / 5.0);
215    }
216}
217
218fn setup_parallax(
219    mut commands: Commands,
220    mut materials: ResMut<Assets<StandardMaterial>>,
221    mut meshes: ResMut<Assets<Mesh>>,
222    asset_server: Res<AssetServer>,
223) {
224    // The normal map. Note that to generate it in the GIMP image editor, you should
225    // open the depth map, and do Filters → Generic → Normal Map
226    // You should enable the "flip X" checkbox.
227    let normal_handle = asset_server.load_with_settings(
228        "textures/parallax_example/cube_normal.png",
229        // The normal map texture is in linear color space. Lighting won't look correct
230        // if `is_srgb` is `true`, which is the default.
231        |settings: &mut ImageLoaderSettings| settings.is_srgb = false,
232    );
233
234    let mut cube = Mesh::from(Cuboid::new(0.15, 0.15, 0.15));
235
236    // NOTE: for normal maps and depth maps to work, the mesh
237    // needs tangents generated.
238    cube.generate_tangents().unwrap();
239
240    let parallax_material = materials.add(StandardMaterial {
241        perceptual_roughness: 0.4,
242        base_color_texture: Some(asset_server.load("textures/parallax_example/cube_color.png")),
243        normal_map_texture: Some(normal_handle),
244        // The depth map is a grayscale texture where black is the highest level and
245        // white the lowest.
246        depth_map: Some(asset_server.load("textures/parallax_example/cube_depth.png")),
247        parallax_depth_scale: 0.09,
248        parallax_mapping_method: ParallaxMappingMethod::Relief { max_steps: 4 },
249        max_parallax_layer_count: ops::exp2(5.0f32),
250        ..default()
251    });
252    commands.spawn((
253        Mesh3d(meshes.add(cube)),
254        MeshMaterial3d(parallax_material),
255        Transform::from_xyz(0.4, 0.2, -0.8),
256        Spin { speed: 0.3 },
257    ));
258}
259#[derive(Component)]
260struct Spin {
261    speed: f32,
262}
263
264fn spin(time: Res<Time>, mut query: Query<(&mut Transform, &Spin)>, pause: Res<Pause>) {
265    if pause.0 {
266        return;
267    }
268    for (mut transform, spin) in query.iter_mut() {
269        transform.rotate_local_y(spin.speed * time.delta_secs());
270        transform.rotate_local_x(spin.speed * time.delta_secs());
271        transform.rotate_local_z(-spin.speed * time.delta_secs());
272    }
273}
274
275#[derive(Resource, Default)]
276enum DefaultRenderMode {
277    #[default]
278    Deferred,
279    Forward,
280    ForwardPrepass,
281}
282
283fn switch_mode(
284    mut text: Single<&mut Text>,
285    mut commands: Commands,
286    keys: Res<ButtonInput<KeyCode>>,
287    mut default_opaque_renderer_method: ResMut<DefaultOpaqueRendererMethod>,
288    mut materials: ResMut<Assets<StandardMaterial>>,
289    cameras: Query<Entity, With<Camera>>,
290    mut pause: ResMut<Pause>,
291    mut hide_ui: Local<bool>,
292    mut mode: Local<DefaultRenderMode>,
293) {
294    text.clear();
295
296    if keys.just_pressed(KeyCode::Space) {
297        pause.0 = !pause.0;
298    }
299
300    if keys.just_pressed(KeyCode::Digit1) {
301        *mode = DefaultRenderMode::Deferred;
302        default_opaque_renderer_method.set_to_deferred();
303        println!("DefaultOpaqueRendererMethod: Deferred");
304        for _ in materials.iter_mut() {}
305        for camera in &cameras {
306            commands.entity(camera).remove::<NormalPrepass>();
307            commands.entity(camera).insert(DepthPrepass);
308            commands.entity(camera).insert(MotionVectorPrepass);
309            commands.entity(camera).insert(DeferredPrepass);
310        }
311    }
312    if keys.just_pressed(KeyCode::Digit2) {
313        *mode = DefaultRenderMode::Forward;
314        default_opaque_renderer_method.set_to_forward();
315        println!("DefaultOpaqueRendererMethod: Forward");
316        for _ in materials.iter_mut() {}
317        for camera in &cameras {
318            commands.entity(camera).remove::<NormalPrepass>();
319            commands.entity(camera).remove::<DepthPrepass>();
320            commands.entity(camera).remove::<MotionVectorPrepass>();
321            commands.entity(camera).remove::<DeferredPrepass>();
322        }
323    }
324    if keys.just_pressed(KeyCode::Digit3) {
325        *mode = DefaultRenderMode::ForwardPrepass;
326        default_opaque_renderer_method.set_to_forward();
327        println!("DefaultOpaqueRendererMethod: Forward + Prepass");
328        for _ in materials.iter_mut() {}
329        for camera in &cameras {
330            commands.entity(camera).insert(NormalPrepass);
331            commands.entity(camera).insert(DepthPrepass);
332            commands.entity(camera).insert(MotionVectorPrepass);
333            commands.entity(camera).remove::<DeferredPrepass>();
334        }
335    }
336
337    if keys.just_pressed(KeyCode::KeyH) {
338        *hide_ui = !*hide_ui;
339    }
340
341    if !*hide_ui {
342        text.push_str("(H) Hide UI\n");
343        text.push_str("(Space) Play/Pause\n\n");
344        text.push_str("Rendering Method:\n");
345
346        text.push_str(&format!(
347            "(1) {} Deferred\n",
348            if let DefaultRenderMode::Deferred = *mode {
349                ">"
350            } else {
351                ""
352            }
353        ));
354        text.push_str(&format!(
355            "(2) {} Forward\n",
356            if let DefaultRenderMode::Forward = *mode {
357                ">"
358            } else {
359                ""
360            }
361        ));
362        text.push_str(&format!(
363            "(3) {} Forward + Prepass\n",
364            if let DefaultRenderMode::ForwardPrepass = *mode {
365                ">"
366            } else {
367                ""
368            }
369        ));
370    }
371}