1use 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#[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
61fn 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 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 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 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 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 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 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 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 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 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 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 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 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 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}