Skip to main content

deferred_rendering/
deferred_rendering.rs

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