Skip to main content

transmission/
transmission.rs

1//! This example showcases light transmission
2//!
3//! ## Controls
4//!
5//! | Key Binding        | Action                                               |
6//! |:-------------------|:-----------------------------------------------------|
7//! | `Space`            | Toggle Screen Space Transmission                     |
8//! | `J`/`K`/`L`/`;`    | Change Screen Space Transmission Quality             |
9//! | `O` / `P`          | Decrease / Increase Screen Space Transmission Steps  |
10//! | `1` / `2`          | Decrease / Increase Diffuse Transmission             |
11//! | `Q` / `W`          | Decrease / Increase Specular Transmission            |
12//! | `A` / `S`          | Decrease / Increase Thickness                        |
13//! | `Z` / `X`          | Decrease / Increase IOR                              |
14//! | `E` / `R`          | Decrease / Increase Perceptual Roughness             |
15//! | `U` / `I`          | Decrease / Increase Reflectance                      |
16//! | Arrow Keys         | Control Camera                                       |
17//! | `C`                | Randomize Colors                                     |
18//! | `H`                | Toggle HDR + Bloom                                   |
19//! | `D`                | Toggle Depth Prepass                                 |
20//! | `T`                | Toggle TAA                                           |
21
22use std::f32::consts::PI;
23
24use bevy::{
25    camera::{Exposure, Hdr},
26    color::palettes::css::*,
27    core_pipeline::{prepass::DepthPrepass, tonemapping::Tonemapping},
28    light::{NotShadowCaster, PointLightShadowMap, TransmittedShadowReceiver},
29    math::ops,
30    pbr::{ScreenSpaceTransmission, ScreenSpaceTransmissionQuality},
31    post_process::bloom::Bloom,
32    prelude::*,
33    render::{
34        camera::TemporalJitter,
35        view::{ColorGrading, ColorGradingGlobal},
36    },
37};
38
39// *Note:* TAA is not _required_ for specular transmission, but
40// it _greatly enhances_ the look of the resulting blur effects.
41// Sadly, it's not available under WebGL.
42#[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))]
43use bevy::anti_alias::taa::TemporalAntiAliasing;
44
45use rand::random;
46
47fn main() {
48    App::new()
49        .add_plugins(DefaultPlugins)
50        .insert_resource(ClearColor(Color::BLACK))
51        .insert_resource(PointLightShadowMap { size: 2048 })
52        .insert_resource(GlobalAmbientLight {
53            brightness: 0.0,
54            ..default()
55        })
56        .add_systems(Startup, setup)
57        .add_systems(Update, (example_control_system, flicker_system))
58        .run();
59}
60
61/// set up a simple 3D scene
62fn setup(
63    mut commands: Commands,
64    mut meshes: ResMut<Assets<Mesh>>,
65    mut materials: ResMut<Assets<StandardMaterial>>,
66    asset_server: Res<AssetServer>,
67) {
68    let icosphere_mesh = meshes.add(Sphere::new(0.9).mesh().ico(7).unwrap());
69    let cube_mesh = meshes.add(Cuboid::new(0.7, 0.7, 0.7));
70    let plane_mesh = meshes.add(Plane3d::default().mesh().size(2.0, 2.0));
71    let cylinder_mesh = meshes.add(Cylinder::new(0.5, 2.0).mesh().resolution(50));
72
73    // Cube #1
74    commands.spawn((
75        Mesh3d(cube_mesh.clone()),
76        MeshMaterial3d(materials.add(StandardMaterial::default())),
77        Transform::from_xyz(0.25, 0.5, -2.0).with_rotation(Quat::from_euler(
78            EulerRot::XYZ,
79            1.4,
80            3.7,
81            21.3,
82        )),
83        ExampleControls {
84            color: true,
85            specular_transmission: false,
86            diffuse_transmission: false,
87        },
88    ));
89
90    // Cube #2
91    commands.spawn((
92        Mesh3d(cube_mesh),
93        MeshMaterial3d(materials.add(StandardMaterial::default())),
94        Transform::from_xyz(-0.75, 0.7, -2.0).with_rotation(Quat::from_euler(
95            EulerRot::XYZ,
96            0.4,
97            2.3,
98            4.7,
99        )),
100        ExampleControls {
101            color: true,
102            specular_transmission: false,
103            diffuse_transmission: false,
104        },
105    ));
106
107    // Candle
108    commands.spawn((
109        Mesh3d(cylinder_mesh),
110        MeshMaterial3d(materials.add(StandardMaterial {
111            base_color: Color::srgb(0.9, 0.2, 0.3),
112            diffuse_transmission: 0.7,
113            perceptual_roughness: 0.32,
114            thickness: 0.2,
115            ..default()
116        })),
117        Transform::from_xyz(-1.0, 0.0, 0.0),
118        ExampleControls {
119            color: true,
120            specular_transmission: false,
121            diffuse_transmission: true,
122        },
123    ));
124
125    // Candle Flame
126    let scaled_white = LinearRgba::from(ANTIQUE_WHITE) * 20.;
127    let scaled_orange = LinearRgba::from(ORANGE_RED) * 4.;
128    let emissive = LinearRgba {
129        red: scaled_white.red + scaled_orange.red,
130        green: scaled_white.green + scaled_orange.green,
131        blue: scaled_white.blue + scaled_orange.blue,
132        alpha: 1.0,
133    };
134
135    commands.spawn((
136        Mesh3d(icosphere_mesh.clone()),
137        MeshMaterial3d(materials.add(StandardMaterial {
138            emissive,
139            diffuse_transmission: 1.0,
140            ..default()
141        })),
142        Transform::from_xyz(-1.0, 1.15, 0.0).with_scale(Vec3::new(0.1, 0.2, 0.1)),
143        Flicker,
144        NotShadowCaster,
145    ));
146
147    // Glass Sphere
148    commands.spawn((
149        Mesh3d(icosphere_mesh.clone()),
150        MeshMaterial3d(materials.add(StandardMaterial {
151            base_color: Color::WHITE,
152            specular_transmission: 0.9,
153            diffuse_transmission: 1.0,
154            thickness: 1.8,
155            ior: 1.5,
156            perceptual_roughness: 0.12,
157            ..default()
158        })),
159        Transform::from_xyz(1.0, 0.0, 0.0),
160        ExampleControls {
161            color: true,
162            specular_transmission: true,
163            diffuse_transmission: false,
164        },
165    ));
166
167    // R Sphere
168    commands.spawn((
169        Mesh3d(icosphere_mesh.clone()),
170        MeshMaterial3d(materials.add(StandardMaterial {
171            base_color: RED.into(),
172            specular_transmission: 0.9,
173            diffuse_transmission: 1.0,
174            thickness: 1.8,
175            ior: 1.5,
176            perceptual_roughness: 0.12,
177            ..default()
178        })),
179        Transform::from_xyz(1.0, -0.5, 2.0).with_scale(Vec3::splat(0.5)),
180        ExampleControls {
181            color: true,
182            specular_transmission: true,
183            diffuse_transmission: false,
184        },
185    ));
186
187    // G Sphere
188    commands.spawn((
189        Mesh3d(icosphere_mesh.clone()),
190        MeshMaterial3d(materials.add(StandardMaterial {
191            base_color: LIME.into(),
192            specular_transmission: 0.9,
193            diffuse_transmission: 1.0,
194            thickness: 1.8,
195            ior: 1.5,
196            perceptual_roughness: 0.12,
197            ..default()
198        })),
199        Transform::from_xyz(0.0, -0.5, 2.0).with_scale(Vec3::splat(0.5)),
200        ExampleControls {
201            color: true,
202            specular_transmission: true,
203            diffuse_transmission: false,
204        },
205    ));
206
207    // B Sphere
208    commands.spawn((
209        Mesh3d(icosphere_mesh),
210        MeshMaterial3d(materials.add(StandardMaterial {
211            base_color: BLUE.into(),
212            specular_transmission: 0.9,
213            diffuse_transmission: 1.0,
214            thickness: 1.8,
215            ior: 1.5,
216            perceptual_roughness: 0.12,
217            ..default()
218        })),
219        Transform::from_xyz(-1.0, -0.5, 2.0).with_scale(Vec3::splat(0.5)),
220        ExampleControls {
221            color: true,
222            specular_transmission: true,
223            diffuse_transmission: false,
224        },
225    ));
226
227    // Chessboard Plane
228    let black_material = materials.add(StandardMaterial {
229        base_color: Color::BLACK,
230        reflectance: 0.3,
231        perceptual_roughness: 0.8,
232        ..default()
233    });
234
235    let white_material = materials.add(StandardMaterial {
236        base_color: Color::WHITE,
237        reflectance: 0.3,
238        perceptual_roughness: 0.8,
239        ..default()
240    });
241
242    for x in -3..4 {
243        for z in -3..4 {
244            commands.spawn((
245                Mesh3d(plane_mesh.clone()),
246                MeshMaterial3d(if (x + z) % 2 == 0 {
247                    black_material.clone()
248                } else {
249                    white_material.clone()
250                }),
251                Transform::from_xyz(x as f32 * 2.0, -1.0, z as f32 * 2.0),
252                ExampleControls {
253                    color: true,
254                    specular_transmission: false,
255                    diffuse_transmission: false,
256                },
257            ));
258        }
259    }
260
261    // Paper
262    commands.spawn((
263        Mesh3d(plane_mesh),
264        MeshMaterial3d(materials.add(StandardMaterial {
265            base_color: Color::WHITE,
266            diffuse_transmission: 0.6,
267            perceptual_roughness: 0.8,
268            reflectance: 1.0,
269            double_sided: true,
270            cull_mode: None,
271            ..default()
272        })),
273        Transform::from_xyz(0.0, 0.5, -3.0)
274            .with_scale(Vec3::new(2.0, 1.0, 1.0))
275            .with_rotation(Quat::from_euler(EulerRot::XYZ, PI / 2.0, 0.0, 0.0)),
276        TransmittedShadowReceiver,
277        ExampleControls {
278            specular_transmission: false,
279            color: false,
280            diffuse_transmission: true,
281        },
282    ));
283
284    // Candle Light
285    commands.spawn((
286        Transform::from_xyz(-1.0, 1.7, 0.0),
287        PointLight {
288            color: Color::from(
289                LinearRgba::from(ANTIQUE_WHITE).mix(&LinearRgba::from(ORANGE_RED), 0.2),
290            ),
291            intensity: 4_000.0,
292            radius: 0.2,
293            range: 5.0,
294            shadow_maps_enabled: true,
295            ..default()
296        },
297        Flicker,
298    ));
299
300    // Camera
301    commands.spawn((
302        Camera3d::default(),
303        ScreenSpaceTransmission::default(),
304        Transform::from_xyz(1.0, 1.8, 7.0).looking_at(Vec3::ZERO, Vec3::Y),
305        ColorGrading {
306            global: ColorGradingGlobal {
307                post_saturation: 1.2,
308                ..default()
309            },
310            ..default()
311        },
312        Tonemapping::TonyMcMapface,
313        Exposure { ev100: 6.0 },
314        #[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))]
315        Msaa::Off,
316        #[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))]
317        TemporalAntiAliasing::default(),
318        EnvironmentMapLight {
319            intensity: 25.0,
320            diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
321            specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
322            ..default()
323        },
324        Bloom::default(),
325    ));
326
327    // Controls Text
328    commands.spawn((
329        Text::default(),
330        Node {
331            position_type: PositionType::Absolute,
332            top: px(12),
333            left: px(12),
334            ..default()
335        },
336        ExampleDisplay,
337    ));
338}
339
340#[derive(Component)]
341struct Flicker;
342
343#[derive(Component)]
344struct ExampleControls {
345    diffuse_transmission: bool,
346    specular_transmission: bool,
347    color: bool,
348}
349
350struct ExampleState {
351    transmission: ScreenSpaceTransmission,
352    diffuse_transmission: f32,
353    specular_transmission: f32,
354    thickness: f32,
355    ior: f32,
356    perceptual_roughness: f32,
357    reflectance: f32,
358    auto_camera: bool,
359}
360
361#[derive(Component)]
362struct ExampleDisplay;
363
364impl Default for ExampleState {
365    fn default() -> Self {
366        ExampleState {
367            transmission: ScreenSpaceTransmission::default(),
368            diffuse_transmission: 0.5,
369            specular_transmission: 0.9,
370            thickness: 1.8,
371            ior: 1.5,
372            perceptual_roughness: 0.12,
373            reflectance: 0.5,
374            auto_camera: true,
375        }
376    }
377}
378
379fn example_control_system(
380    mut commands: Commands,
381    mut materials: ResMut<Assets<StandardMaterial>>,
382    controllable: Query<(&MeshMaterial3d<StandardMaterial>, &ExampleControls)>,
383    camera: Single<
384        (
385            Entity,
386            Option<&mut ScreenSpaceTransmission>,
387            &mut Transform,
388            Option<&DepthPrepass>,
389            Option<&TemporalJitter>,
390            Has<Hdr>,
391        ),
392        With<Camera3d>,
393    >,
394    mut display: Single<&mut Text, With<ExampleDisplay>>,
395    mut state: Local<ExampleState>,
396    time: Res<Time>,
397    input: Res<ButtonInput<KeyCode>>,
398) {
399    if input.pressed(KeyCode::Digit2) {
400        state.diffuse_transmission = (state.diffuse_transmission + time.delta_secs()).min(1.0);
401    } else if input.pressed(KeyCode::Digit1) {
402        state.diffuse_transmission = (state.diffuse_transmission - time.delta_secs()).max(0.0);
403    }
404
405    if input.pressed(KeyCode::KeyW) {
406        state.specular_transmission = (state.specular_transmission + time.delta_secs()).min(1.0);
407    } else if input.pressed(KeyCode::KeyQ) {
408        state.specular_transmission = (state.specular_transmission - time.delta_secs()).max(0.0);
409    }
410
411    if input.pressed(KeyCode::KeyS) {
412        state.thickness = (state.thickness + time.delta_secs()).min(5.0);
413    } else if input.pressed(KeyCode::KeyA) {
414        state.thickness = (state.thickness - time.delta_secs()).max(0.0);
415    }
416
417    if input.pressed(KeyCode::KeyX) {
418        state.ior = (state.ior + time.delta_secs()).min(3.0);
419    } else if input.pressed(KeyCode::KeyZ) {
420        state.ior = (state.ior - time.delta_secs()).max(1.0);
421    }
422
423    if input.pressed(KeyCode::KeyI) {
424        state.reflectance = (state.reflectance + time.delta_secs()).min(1.0);
425    } else if input.pressed(KeyCode::KeyU) {
426        state.reflectance = (state.reflectance - time.delta_secs()).max(0.0);
427    }
428
429    if input.pressed(KeyCode::KeyR) {
430        state.perceptual_roughness = (state.perceptual_roughness + time.delta_secs()).min(1.0);
431    } else if input.pressed(KeyCode::KeyE) {
432        state.perceptual_roughness = (state.perceptual_roughness - time.delta_secs()).max(0.0);
433    }
434
435    let randomize_colors = input.just_pressed(KeyCode::KeyC);
436
437    for (material_handle, controls) in &controllable {
438        let mut material = materials.get_mut(material_handle).unwrap();
439        if controls.specular_transmission {
440            material.specular_transmission = state.specular_transmission;
441            material.thickness = state.thickness;
442            material.ior = state.ior;
443            material.perceptual_roughness = state.perceptual_roughness;
444            material.reflectance = state.reflectance;
445        }
446
447        if controls.diffuse_transmission {
448            material.diffuse_transmission = state.diffuse_transmission;
449        }
450
451        if controls.color && randomize_colors {
452            material.base_color =
453                Color::srgba(random(), random(), random(), material.base_color.alpha());
454        }
455    }
456
457    let (
458        camera_entity,
459        mut transmission,
460        mut camera_transform,
461        depth_prepass,
462        temporal_jitter,
463        hdr,
464    ) = camera.into_inner();
465
466    if input.just_pressed(KeyCode::KeyH) {
467        if hdr {
468            commands.entity(camera_entity).remove::<Hdr>();
469        } else {
470            commands.entity(camera_entity).insert(Hdr);
471        }
472    }
473
474    #[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))]
475    if input.just_pressed(KeyCode::KeyD) {
476        if depth_prepass.is_none() {
477            commands.entity(camera_entity).insert(DepthPrepass);
478        } else {
479            commands.entity(camera_entity).remove::<DepthPrepass>();
480        }
481    }
482
483    #[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))]
484    if input.just_pressed(KeyCode::KeyT) {
485        if temporal_jitter.is_none() {
486            commands
487                .entity(camera_entity)
488                .insert((TemporalJitter::default(), TemporalAntiAliasing::default()));
489        } else {
490            commands
491                .entity(camera_entity)
492                .remove::<(TemporalJitter, TemporalAntiAliasing)>();
493        }
494    }
495
496    if input.just_pressed(KeyCode::KeyO) && state.transmission.steps > 0 {
497        state.transmission.steps -= 1;
498    }
499
500    if input.just_pressed(KeyCode::KeyP) && state.transmission.steps < 4 {
501        state.transmission.steps += 1;
502    }
503
504    if input.just_pressed(KeyCode::KeyJ) {
505        state.transmission.quality = ScreenSpaceTransmissionQuality::Low;
506    }
507
508    if input.just_pressed(KeyCode::KeyK) {
509        state.transmission.quality = ScreenSpaceTransmissionQuality::Medium;
510    }
511
512    if input.just_pressed(KeyCode::KeyL) {
513        state.transmission.quality = ScreenSpaceTransmissionQuality::High;
514    }
515
516    if input.just_pressed(KeyCode::Semicolon) {
517        state.transmission.quality = ScreenSpaceTransmissionQuality::Ultra;
518    }
519
520    if let Some(transmission) = &mut transmission {
521        **transmission = state.transmission.clone();
522    }
523
524    if input.just_pressed(KeyCode::Space) {
525        if transmission.is_some() {
526            commands
527                .entity(camera_entity)
528                .remove::<ScreenSpaceTransmission>();
529        } else {
530            commands
531                .entity(camera_entity)
532                .insert(state.transmission.clone());
533        }
534    }
535
536    let rotation = if input.pressed(KeyCode::ArrowRight) {
537        state.auto_camera = false;
538        time.delta_secs()
539    } else if input.pressed(KeyCode::ArrowLeft) {
540        state.auto_camera = false;
541        -time.delta_secs()
542    } else if state.auto_camera {
543        time.delta_secs() * 0.25
544    } else {
545        0.0
546    };
547
548    let distance_change =
549        if input.pressed(KeyCode::ArrowDown) && camera_transform.translation.length() < 25.0 {
550            time.delta_secs()
551        } else if input.pressed(KeyCode::ArrowUp) && camera_transform.translation.length() > 2.0 {
552            -time.delta_secs()
553        } else {
554            0.0
555        };
556
557    camera_transform.translation *= ops::exp(distance_change);
558
559    camera_transform.rotate_around(
560        Vec3::ZERO,
561        Quat::from_euler(EulerRot::XYZ, 0.0, rotation, 0.0),
562    );
563
564    display.0 = format!(
565        concat!(
566            "         Space  Screen Space Specular Transmission: {}\n",
567            " J / K / L / ;  Screen Space Specular Transmissive Quality: {:?}\n",
568            "         O / P  Screen Space Specular Transmissive Steps: {}\n",
569            "         1 / 2  Diffuse Transmission: {:.2}\n",
570            "         Q / W  Specular Transmission: {:.2}\n",
571            "         A / S  Thickness: {:.2}\n",
572            "         Z / X  IOR: {:.2}\n",
573            "         E / R  Perceptual Roughness: {:.2}\n",
574            "         U / I  Reflectance: {:.2}\n",
575            "    Arrow Keys  Control Camera\n",
576            "             C  Randomize Colors\n",
577            "             H  HDR + Bloom: {}\n",
578            "             D  Depth Prepass: {}\n",
579            "             T  TAA: {}\n",
580        ),
581        if transmission.is_some() { "ON" } else { "OFF" },
582        state.transmission.quality,
583        state.transmission.steps,
584        state.diffuse_transmission,
585        state.specular_transmission,
586        state.thickness,
587        state.ior,
588        state.perceptual_roughness,
589        state.reflectance,
590        if hdr { "ON " } else { "OFF" },
591        if cfg!(any(feature = "webgpu", not(target_arch = "wasm32"))) {
592            if depth_prepass.is_some() {
593                "ON "
594            } else {
595                "OFF"
596            }
597        } else {
598            "N/A (WebGL)"
599        },
600        if cfg!(any(feature = "webgpu", not(target_arch = "wasm32"))) {
601            if temporal_jitter.is_some() {
602                if depth_prepass.is_some() {
603                    "ON "
604                } else {
605                    "N/A (Needs Depth Prepass)"
606                }
607            } else {
608                "OFF"
609            }
610        } else {
611            "N/A (WebGL)"
612        },
613    );
614}
615
616fn flicker_system(
617    mut flame: Single<&mut Transform, (With<Flicker>, With<Mesh3d>)>,
618    light: Single<(&mut PointLight, &mut Transform), (With<Flicker>, Without<Mesh3d>)>,
619    time: Res<Time>,
620) {
621    let s = time.elapsed_secs();
622    let a = ops::cos(s * 6.0) * 0.0125 + ops::cos(s * 4.0) * 0.025;
623    let b = ops::cos(s * 5.0) * 0.0125 + ops::cos(s * 3.0) * 0.025;
624    let c = ops::cos(s * 7.0) * 0.0125 + ops::cos(s * 2.0) * 0.025;
625    let (mut light, mut light_transform) = light.into_inner();
626    light.intensity = 4_000.0 + 3000.0 * (a + b + c);
627    flame.translation = Vec3::new(-1.0, 1.23, 0.0);
628    flame.look_at(Vec3::new(-1.0 - c, 1.7 - b, 0.0 - a), Vec3::X);
629    flame.rotate(Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, PI / 2.0));
630    light_transform.translation = Vec3::new(-1.0 - c, 1.7, 0.0 - a);
631    flame.translation = Vec3::new(-1.0 - c, 1.23, 0.0 - a);
632}