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