1use bevy::{
4 core_pipeline::tonemapping::Tonemapping,
5 math::ops,
6 post_process::bloom::{Bloom, BloomCompositeMode},
7 prelude::*,
8};
9use std::{
10 collections::hash_map::DefaultHasher,
11 hash::{Hash, Hasher},
12};
13
14fn main() {
15 App::new()
16 .add_plugins(DefaultPlugins)
17 .add_systems(Startup, setup_scene)
18 .add_systems(Update, (update_bloom_settings, bounce_spheres))
19 .run();
20}
21
22fn setup_scene(
23 mut commands: Commands,
24 mut meshes: ResMut<Assets<Mesh>>,
25 mut materials: ResMut<Assets<StandardMaterial>>,
26) {
27 commands.spawn((
28 Camera3d::default(),
29 Camera {
30 clear_color: ClearColorConfig::Custom(Color::BLACK),
31 ..default()
32 },
33 Tonemapping::TonyMcMapface, Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
35 Bloom::NATURAL, ));
37
38 let material_emissive1 = materials.add(StandardMaterial {
39 emissive: LinearRgba::rgb(0.0, 0.0, 150.0), ..default()
41 });
42 let material_emissive2 = materials.add(StandardMaterial {
43 emissive: LinearRgba::rgb(1000.0, 1000.0, 1000.0),
44 ..default()
45 });
46 let material_emissive3 = materials.add(StandardMaterial {
47 emissive: LinearRgba::rgb(50.0, 0.0, 0.0),
48 ..default()
49 });
50 let material_non_emissive = materials.add(StandardMaterial {
51 base_color: Color::BLACK,
52 ..default()
53 });
54
55 let mesh = meshes.add(Sphere::new(0.4).mesh().ico(5).unwrap());
56
57 for x in -5..5 {
58 for z in -5..5 {
59 let mut hasher = DefaultHasher::new();
62 (x, z).hash(&mut hasher);
63 let rand = (hasher.finish() + 3) % 6;
64
65 let (material, scale) = match rand {
66 0 => (material_emissive1.clone(), 0.5),
67 1 => (material_emissive2.clone(), 0.1),
68 2 => (material_emissive3.clone(), 1.0),
69 3..=5 => (material_non_emissive.clone(), 1.5),
70 _ => unreachable!(),
71 };
72
73 commands.spawn((
74 Mesh3d(mesh.clone()),
75 MeshMaterial3d(material),
76 Transform::from_xyz(x as f32 * 2.0, 0.0, z as f32 * 2.0)
77 .with_scale(Vec3::splat(scale)),
78 Bouncing,
79 ));
80 }
81 }
82
83 commands.spawn((
85 Text::default(),
86 Node {
87 position_type: PositionType::Absolute,
88 bottom: px(12),
89 left: px(12),
90 ..default()
91 },
92 ));
93}
94
95fn update_bloom_settings(
98 camera: Single<(Entity, Option<&mut Bloom>), With<Camera>>,
99 mut text: Single<&mut Text>,
100 mut commands: Commands,
101 keycode: Res<ButtonInput<KeyCode>>,
102 time: Res<Time>,
103) {
104 let bloom = camera.into_inner();
105
106 match bloom {
107 (entity, Some(mut bloom)) => {
108 text.0 = "Bloom (Toggle: Space)\n".to_string();
109 text.push_str(&format!("(Q/A) Intensity: {:.2}\n", bloom.intensity));
110 text.push_str(&format!(
111 "(W/S) Low-frequency boost: {:.2}\n",
112 bloom.low_frequency_boost
113 ));
114 text.push_str(&format!(
115 "(E/D) Low-frequency boost curvature: {:.2}\n",
116 bloom.low_frequency_boost_curvature
117 ));
118 text.push_str(&format!(
119 "(R/F) High-pass frequency: {:.2}\n",
120 bloom.high_pass_frequency
121 ));
122 text.push_str(&format!(
123 "(T/G) Mode: {}\n",
124 match bloom.composite_mode {
125 BloomCompositeMode::EnergyConserving => "Energy-conserving",
126 BloomCompositeMode::Additive => "Additive",
127 }
128 ));
129 text.push_str(&format!(
130 "(Y/H) Threshold: {:.2}\n",
131 bloom.prefilter.threshold
132 ));
133 text.push_str(&format!(
134 "(U/J) Threshold softness: {:.2}\n",
135 bloom.prefilter.threshold_softness
136 ));
137 text.push_str(&format!("(I/K) Horizontal Scale: {:.2}\n", bloom.scale.x));
138
139 if keycode.just_pressed(KeyCode::Space) {
140 commands.entity(entity).remove::<Bloom>();
141 }
142
143 let dt = time.delta_secs();
144
145 if keycode.pressed(KeyCode::KeyA) {
146 bloom.intensity -= dt / 10.0;
147 }
148 if keycode.pressed(KeyCode::KeyQ) {
149 bloom.intensity += dt / 10.0;
150 }
151 bloom.intensity = bloom.intensity.clamp(0.0, 1.0);
152
153 if keycode.pressed(KeyCode::KeyS) {
154 bloom.low_frequency_boost -= dt / 10.0;
155 }
156 if keycode.pressed(KeyCode::KeyW) {
157 bloom.low_frequency_boost += dt / 10.0;
158 }
159 bloom.low_frequency_boost = bloom.low_frequency_boost.clamp(0.0, 1.0);
160
161 if keycode.pressed(KeyCode::KeyD) {
162 bloom.low_frequency_boost_curvature -= dt / 10.0;
163 }
164 if keycode.pressed(KeyCode::KeyE) {
165 bloom.low_frequency_boost_curvature += dt / 10.0;
166 }
167 bloom.low_frequency_boost_curvature =
168 bloom.low_frequency_boost_curvature.clamp(0.0, 1.0);
169
170 if keycode.pressed(KeyCode::KeyF) {
171 bloom.high_pass_frequency -= dt / 10.0;
172 }
173 if keycode.pressed(KeyCode::KeyR) {
174 bloom.high_pass_frequency += dt / 10.0;
175 }
176 bloom.high_pass_frequency = bloom.high_pass_frequency.clamp(0.0, 1.0);
177
178 if keycode.pressed(KeyCode::KeyG) {
179 bloom.composite_mode = BloomCompositeMode::Additive;
180 }
181 if keycode.pressed(KeyCode::KeyT) {
182 bloom.composite_mode = BloomCompositeMode::EnergyConserving;
183 }
184
185 if keycode.pressed(KeyCode::KeyH) {
186 bloom.prefilter.threshold -= dt;
187 }
188 if keycode.pressed(KeyCode::KeyY) {
189 bloom.prefilter.threshold += dt;
190 }
191 bloom.prefilter.threshold = bloom.prefilter.threshold.max(0.0);
192
193 if keycode.pressed(KeyCode::KeyJ) {
194 bloom.prefilter.threshold_softness -= dt / 10.0;
195 }
196 if keycode.pressed(KeyCode::KeyU) {
197 bloom.prefilter.threshold_softness += dt / 10.0;
198 }
199 bloom.prefilter.threshold_softness = bloom.prefilter.threshold_softness.clamp(0.0, 1.0);
200
201 if keycode.pressed(KeyCode::KeyK) {
202 bloom.scale.x -= dt * 2.0;
203 }
204 if keycode.pressed(KeyCode::KeyI) {
205 bloom.scale.x += dt * 2.0;
206 }
207 bloom.scale.x = bloom.scale.x.clamp(0.0, 8.0);
208 }
209
210 (entity, None) => {
211 text.0 = "Bloom: Off (Toggle: Space)".to_string();
212
213 if keycode.just_pressed(KeyCode::Space) {
214 commands.entity(entity).insert(Bloom::NATURAL);
215 }
216 }
217 }
218}
219
220#[derive(Component)]
221struct Bouncing;
222
223fn bounce_spheres(time: Res<Time>, mut query: Query<&mut Transform, With<Bouncing>>) {
224 for mut transform in query.iter_mut() {
225 transform.translation.y =
226 ops::sin(transform.translation.x + transform.translation.z + time.elapsed_secs());
227 }
228}