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