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