Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/fog.rs
6592 views
1
//! Distance-based fog visual effects are used in many games to give a soft falloff of visibility to the player for performance and/or visual design reasons. The further away something in a 3D world is from the camera, the more it's mixed or completely overwritten by a given color.
2
//!
3
//! In Bevy we can add the [`DistanceFog`] component to the same entity as our [`Camera3d`] to apply a distance fog effect. It has fields for color, directional light parameters, and how the fog falls off over distance. And that's it! The distance fog is now applied to the camera.
4
//!
5
//! The [`FogFalloff`] field controls most of the behavior of the fog through different descriptions of fog "curves". I.e. [`FogFalloff::Linear`] lets us define a start and end distance where up until the start distance none of the fog color is mixed in and by the end distance the fog color is as mixed in as it can be. [`FogFalloff::Exponential`] on the other hand uses an exponential curve to drive how "visible" things are with a density value.
6
//!
7
//! [Atmospheric fog](https://bevy.org/examples/3d-rendering/atmospheric-fog/) is another fog type that uses this same method of setup, but isn't covered here as it is a kind of fog that is most often used to imply distance and size in clear weather, while the ones shown off here are much more "dense".
8
//!
9
//! The bulk of this example is spent building a scene that suites showing off that the fog is working as intended by creating a pyramid (a 3D structure with clear delineations), a light source, input handling to modify fog settings, and UI to show what the current fog settings are.
10
//!
11
//! ## Controls
12
//!
13
//! | Key Binding | Action |
14
//! |:-------------------|:------------------------------------|
15
//! | `1` / `2` / `3` | Fog Falloff Mode |
16
//! | `A` / `S` | Move Start Distance (Linear Fog) |
17
//! | | Change Density (Exponential Fogs) |
18
//! | `Z` / `X` | Move End Distance (Linear Fog) |
19
//! | `-` / `=` | Adjust Fog Red Channel |
20
//! | `[` / `]` | Adjust Fog Green Channel |
21
//! | `;` / `'` | Adjust Fog Blue Channel |
22
//! | `.` / `?` | Adjust Fog Alpha Channel |
23
24
use bevy::{
25
light::{NotShadowCaster, NotShadowReceiver},
26
math::ops,
27
prelude::*,
28
};
29
30
fn main() {
31
App::new()
32
.insert_resource(AmbientLight::NONE)
33
.add_plugins(DefaultPlugins)
34
.add_systems(
35
Startup,
36
(setup_camera_fog, setup_pyramid_scene, setup_instructions),
37
)
38
.add_systems(Update, update_system)
39
.run();
40
}
41
42
fn setup_camera_fog(mut commands: Commands) {
43
commands.spawn((
44
Camera3d::default(),
45
DistanceFog {
46
color: Color::srgb(0.25, 0.25, 0.25),
47
falloff: FogFalloff::Linear {
48
start: 5.0,
49
end: 20.0,
50
},
51
..default()
52
},
53
));
54
}
55
56
fn setup_pyramid_scene(
57
mut commands: Commands,
58
mut meshes: ResMut<Assets<Mesh>>,
59
mut materials: ResMut<Assets<StandardMaterial>>,
60
) {
61
let stone = materials.add(StandardMaterial {
62
base_color: Srgba::hex("28221B").unwrap().into(),
63
perceptual_roughness: 1.0,
64
..default()
65
});
66
67
// pillars
68
for (x, z) in &[(-1.5, -1.5), (1.5, -1.5), (1.5, 1.5), (-1.5, 1.5)] {
69
commands.spawn((
70
Mesh3d(meshes.add(Cuboid::new(1.0, 3.0, 1.0))),
71
MeshMaterial3d(stone.clone()),
72
Transform::from_xyz(*x, 1.5, *z),
73
));
74
}
75
76
// orb
77
commands.spawn((
78
Mesh3d(meshes.add(Sphere::default())),
79
MeshMaterial3d(materials.add(StandardMaterial {
80
base_color: Srgba::hex("126212CC").unwrap().into(),
81
reflectance: 1.0,
82
perceptual_roughness: 0.0,
83
metallic: 0.5,
84
alpha_mode: AlphaMode::Blend,
85
..default()
86
})),
87
Transform::from_scale(Vec3::splat(1.75)).with_translation(Vec3::new(0.0, 4.0, 0.0)),
88
NotShadowCaster,
89
NotShadowReceiver,
90
));
91
92
// steps
93
for i in 0..50 {
94
let half_size = i as f32 / 2.0 + 3.0;
95
let y = -i as f32 / 2.0;
96
commands.spawn((
97
Mesh3d(meshes.add(Cuboid::new(2.0 * half_size, 0.5, 2.0 * half_size))),
98
MeshMaterial3d(stone.clone()),
99
Transform::from_xyz(0.0, y + 0.25, 0.0),
100
));
101
}
102
103
// sky
104
commands.spawn((
105
Mesh3d(meshes.add(Cuboid::new(2.0, 1.0, 1.0))),
106
MeshMaterial3d(materials.add(StandardMaterial {
107
base_color: Srgba::hex("888888").unwrap().into(),
108
unlit: true,
109
cull_mode: None,
110
..default()
111
})),
112
Transform::from_scale(Vec3::splat(1_000_000.0)),
113
));
114
115
// light
116
commands.spawn((
117
PointLight {
118
shadows_enabled: true,
119
..default()
120
},
121
Transform::from_xyz(0.0, 1.0, 0.0),
122
));
123
}
124
125
fn setup_instructions(mut commands: Commands) {
126
commands.spawn((
127
Text::default(),
128
Node {
129
position_type: PositionType::Absolute,
130
top: px(12),
131
left: px(12),
132
..default()
133
},
134
));
135
}
136
137
fn update_system(
138
camera: Single<(&mut DistanceFog, &mut Transform)>,
139
mut text: Single<&mut Text>,
140
time: Res<Time>,
141
keycode: Res<ButtonInput<KeyCode>>,
142
) {
143
let now = time.elapsed_secs();
144
let delta = time.delta_secs();
145
146
let (mut fog, mut transform) = camera.into_inner();
147
148
// Orbit camera around pyramid
149
let orbit_scale = 8.0 + ops::sin(now / 10.0) * 7.0;
150
*transform = Transform::from_xyz(
151
ops::cos(now / 5.0) * orbit_scale,
152
12.0 - orbit_scale / 2.0,
153
ops::sin(now / 5.0) * orbit_scale,
154
)
155
.looking_at(Vec3::ZERO, Vec3::Y);
156
157
// Fog Information
158
text.0 = format!("Fog Falloff: {:?}\nFog Color: {:?}", fog.falloff, fog.color);
159
160
// Fog Falloff Mode Switching
161
text.push_str("\n\n1 / 2 / 3 - Fog Falloff Mode");
162
163
if keycode.pressed(KeyCode::Digit1) {
164
if let FogFalloff::Linear { .. } = fog.falloff {
165
// No change
166
} else {
167
fog.falloff = FogFalloff::Linear {
168
start: 5.0,
169
end: 20.0,
170
};
171
};
172
}
173
174
if keycode.pressed(KeyCode::Digit2) {
175
if let FogFalloff::Exponential { .. } = fog.falloff {
176
// No change
177
} else if let FogFalloff::ExponentialSquared { density } = fog.falloff {
178
fog.falloff = FogFalloff::Exponential { density };
179
} else {
180
fog.falloff = FogFalloff::Exponential { density: 0.07 };
181
};
182
}
183
184
if keycode.pressed(KeyCode::Digit3) {
185
if let FogFalloff::Exponential { density } = fog.falloff {
186
fog.falloff = FogFalloff::ExponentialSquared { density };
187
} else if let FogFalloff::ExponentialSquared { .. } = fog.falloff {
188
// No change
189
} else {
190
fog.falloff = FogFalloff::ExponentialSquared { density: 0.07 };
191
};
192
}
193
194
// Linear Fog Controls
195
if let FogFalloff::Linear { start, end } = &mut fog.falloff {
196
text.push_str("\nA / S - Move Start Distance\nZ / X - Move End Distance");
197
198
if keycode.pressed(KeyCode::KeyA) {
199
*start -= delta * 3.0;
200
}
201
if keycode.pressed(KeyCode::KeyS) {
202
*start += delta * 3.0;
203
}
204
if keycode.pressed(KeyCode::KeyZ) {
205
*end -= delta * 3.0;
206
}
207
if keycode.pressed(KeyCode::KeyX) {
208
*end += delta * 3.0;
209
}
210
}
211
212
// Exponential Fog Controls
213
if let FogFalloff::Exponential { density } = &mut fog.falloff {
214
text.push_str("\nA / S - Change Density");
215
216
if keycode.pressed(KeyCode::KeyA) {
217
*density -= delta * 0.5 * *density;
218
if *density < 0.0 {
219
*density = 0.0;
220
}
221
}
222
if keycode.pressed(KeyCode::KeyS) {
223
*density += delta * 0.5 * *density;
224
}
225
}
226
227
// ExponentialSquared Fog Controls
228
if let FogFalloff::ExponentialSquared { density } = &mut fog.falloff {
229
text.push_str("\nA / S - Change Density");
230
231
if keycode.pressed(KeyCode::KeyA) {
232
*density -= delta * 0.5 * *density;
233
if *density < 0.0 {
234
*density = 0.0;
235
}
236
}
237
if keycode.pressed(KeyCode::KeyS) {
238
*density += delta * 0.5 * *density;
239
}
240
}
241
242
// RGBA Controls
243
text.push_str("\n\n- / = - Red\n[ / ] - Green\n; / ' - Blue\n. / ? - Alpha");
244
245
// We're performing various operations in the sRGB color space,
246
// so we convert the fog color to sRGB here, then modify it,
247
// and finally when we're done we can convert it back and set it.
248
let mut fog_color = Srgba::from(fog.color);
249
if keycode.pressed(KeyCode::Minus) {
250
fog_color.red = (fog_color.red - 0.1 * delta).max(0.0);
251
}
252
253
if keycode.any_pressed([KeyCode::Equal, KeyCode::NumpadEqual]) {
254
fog_color.red = (fog_color.red + 0.1 * delta).min(1.0);
255
}
256
257
if keycode.pressed(KeyCode::BracketLeft) {
258
fog_color.green = (fog_color.green - 0.1 * delta).max(0.0);
259
}
260
261
if keycode.pressed(KeyCode::BracketRight) {
262
fog_color.green = (fog_color.green + 0.1 * delta).min(1.0);
263
}
264
265
if keycode.pressed(KeyCode::Semicolon) {
266
fog_color.blue = (fog_color.blue - 0.1 * delta).max(0.0);
267
}
268
269
if keycode.pressed(KeyCode::Quote) {
270
fog_color.blue = (fog_color.blue + 0.1 * delta).min(1.0);
271
}
272
273
if keycode.pressed(KeyCode::Period) {
274
fog_color.alpha = (fog_color.alpha - 0.1 * delta).max(0.0);
275
}
276
277
if keycode.pressed(KeyCode::Slash) {
278
fog_color.alpha = (fog_color.alpha + 0.1 * delta).min(1.0);
279
}
280
281
fog.color = Color::from(fog_color);
282
}
283
284