Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_pbr/src/atmosphere/mod.rs
6604 views
1
//! Procedural Atmospheric Scattering.
2
//!
3
//! This plugin implements [Hillaire's 2020 paper](https://sebh.github.io/publications/egsr2020.pdf)
4
//! on real-time atmospheric scattering. While it *will* work simply as a
5
//! procedural skybox, it also does much more. It supports dynamic time-of-
6
//! -day, multiple directional lights, and since it's applied as a post-processing
7
//! effect *on top* of the existing skybox, a starry skybox would automatically
8
//! show based on the time of day. Scattering in front of terrain (similar
9
//! to distance fog, but more complex) is handled as well, and takes into
10
//! account the directional light color and direction.
11
//!
12
//! Adding the [`Atmosphere`] component to a 3d camera will enable the effect,
13
//! which by default is set to look similar to Earth's atmosphere. See the
14
//! documentation on the component itself for information regarding its fields.
15
//!
16
//! Performance-wise, the effect should be fairly cheap since the LUTs (Look
17
//! Up Tables) that encode most of the data are small, and take advantage of the
18
//! fact that the atmosphere is symmetric. Performance is also proportional to
19
//! the number of directional lights in the scene. In order to tune
20
//! performance more finely, the [`AtmosphereSettings`] camera component
21
//! manages the size of each LUT and the sample count for each ray.
22
//!
23
//! Given how similar it is to [`crate::volumetric_fog`], it might be expected
24
//! that these two modules would work together well. However for now using both
25
//! at once is untested, and might not be physically accurate. These may be
26
//! integrated into a single module in the future.
27
//!
28
//! On web platforms, atmosphere rendering will look slightly different. Specifically, when calculating how light travels
29
//! through the atmosphere, we use a simpler averaging technique instead of the more
30
//! complex blending operations. This difference will be resolved for WebGPU in a future release.
31
//!
32
//! [Shadertoy]: https://www.shadertoy.com/view/slSXRW
33
//!
34
//! [Unreal Engine Implementation]: https://github.com/sebh/UnrealEngineSkyAtmosphere
35
36
mod environment;
37
mod node;
38
pub mod resources;
39
40
use bevy_app::{App, Plugin, Update};
41
use bevy_asset::embedded_asset;
42
use bevy_camera::Camera3d;
43
use bevy_core_pipeline::core_3d::graph::Node3d;
44
use bevy_ecs::{
45
component::Component,
46
query::{Changed, QueryItem, With},
47
schedule::IntoScheduleConfigs,
48
system::{lifetimeless::Read, Query},
49
};
50
use bevy_math::{UVec2, UVec3, Vec3};
51
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
52
use bevy_render::{
53
extract_component::UniformComponentPlugin,
54
render_resource::{DownlevelFlags, ShaderType, SpecializedRenderPipelines},
55
view::Hdr,
56
RenderStartup,
57
};
58
use bevy_render::{
59
extract_component::{ExtractComponent, ExtractComponentPlugin},
60
render_graph::{RenderGraphExt, ViewNodeRunner},
61
render_resource::{TextureFormat, TextureUsages},
62
renderer::RenderAdapter,
63
Render, RenderApp, RenderSystems,
64
};
65
66
use bevy_core_pipeline::core_3d::graph::Core3d;
67
use bevy_shader::load_shader_library;
68
use environment::{
69
init_atmosphere_probe_layout, init_atmosphere_probe_pipeline,
70
prepare_atmosphere_probe_bind_groups, prepare_atmosphere_probe_components,
71
prepare_probe_textures, AtmosphereEnvironmentMap, EnvironmentNode,
72
};
73
use resources::{
74
prepare_atmosphere_transforms, queue_render_sky_pipelines, AtmosphereTransforms,
75
RenderSkyBindGroupLayouts,
76
};
77
use tracing::warn;
78
79
use self::{
80
node::{AtmosphereLutsNode, AtmosphereNode, RenderSkyNode},
81
resources::{
82
prepare_atmosphere_bind_groups, prepare_atmosphere_textures, AtmosphereBindGroupLayouts,
83
AtmosphereLutPipelines, AtmosphereSamplers,
84
},
85
};
86
87
#[doc(hidden)]
88
pub struct AtmospherePlugin;
89
90
impl Plugin for AtmospherePlugin {
91
fn build(&self, app: &mut App) {
92
load_shader_library!(app, "types.wgsl");
93
load_shader_library!(app, "functions.wgsl");
94
load_shader_library!(app, "bruneton_functions.wgsl");
95
load_shader_library!(app, "bindings.wgsl");
96
97
embedded_asset!(app, "transmittance_lut.wgsl");
98
embedded_asset!(app, "multiscattering_lut.wgsl");
99
embedded_asset!(app, "sky_view_lut.wgsl");
100
embedded_asset!(app, "aerial_view_lut.wgsl");
101
embedded_asset!(app, "render_sky.wgsl");
102
embedded_asset!(app, "environment.wgsl");
103
104
app.add_plugins((
105
ExtractComponentPlugin::<Atmosphere>::default(),
106
ExtractComponentPlugin::<GpuAtmosphereSettings>::default(),
107
ExtractComponentPlugin::<AtmosphereEnvironmentMap>::default(),
108
UniformComponentPlugin::<Atmosphere>::default(),
109
UniformComponentPlugin::<GpuAtmosphereSettings>::default(),
110
))
111
.add_systems(Update, prepare_atmosphere_probe_components);
112
}
113
114
fn finish(&self, app: &mut App) {
115
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
116
return;
117
};
118
119
let render_adapter = render_app.world().resource::<RenderAdapter>();
120
121
if !render_adapter
122
.get_downlevel_capabilities()
123
.flags
124
.contains(DownlevelFlags::COMPUTE_SHADERS)
125
{
126
warn!("AtmospherePlugin not loaded. GPU lacks support for compute shaders.");
127
return;
128
}
129
130
if !render_adapter
131
.get_texture_format_features(TextureFormat::Rgba16Float)
132
.allowed_usages
133
.contains(TextureUsages::STORAGE_BINDING)
134
{
135
warn!("AtmospherePlugin not loaded. GPU lacks support: TextureFormat::Rgba16Float does not support TextureUsages::STORAGE_BINDING.");
136
return;
137
}
138
139
render_app
140
.init_resource::<AtmosphereBindGroupLayouts>()
141
.init_resource::<RenderSkyBindGroupLayouts>()
142
.init_resource::<AtmosphereSamplers>()
143
.init_resource::<AtmosphereLutPipelines>()
144
.init_resource::<AtmosphereTransforms>()
145
.init_resource::<SpecializedRenderPipelines<RenderSkyBindGroupLayouts>>()
146
.add_systems(
147
RenderStartup,
148
(init_atmosphere_probe_layout, init_atmosphere_probe_pipeline).chain(),
149
)
150
.add_systems(
151
Render,
152
(
153
configure_camera_depth_usages.in_set(RenderSystems::ManageViews),
154
queue_render_sky_pipelines.in_set(RenderSystems::Queue),
155
prepare_atmosphere_textures.in_set(RenderSystems::PrepareResources),
156
prepare_probe_textures
157
.in_set(RenderSystems::PrepareResources)
158
.after(prepare_atmosphere_textures),
159
prepare_atmosphere_probe_bind_groups.in_set(RenderSystems::PrepareBindGroups),
160
prepare_atmosphere_transforms.in_set(RenderSystems::PrepareResources),
161
prepare_atmosphere_bind_groups.in_set(RenderSystems::PrepareBindGroups),
162
),
163
)
164
.add_render_graph_node::<ViewNodeRunner<AtmosphereLutsNode>>(
165
Core3d,
166
AtmosphereNode::RenderLuts,
167
)
168
.add_render_graph_edges(
169
Core3d,
170
(
171
// END_PRE_PASSES -> RENDER_LUTS -> MAIN_PASS
172
Node3d::EndPrepasses,
173
AtmosphereNode::RenderLuts,
174
Node3d::StartMainPass,
175
),
176
)
177
.add_render_graph_node::<ViewNodeRunner<RenderSkyNode>>(
178
Core3d,
179
AtmosphereNode::RenderSky,
180
)
181
.add_render_graph_node::<EnvironmentNode>(Core3d, AtmosphereNode::Environment)
182
.add_render_graph_edges(
183
Core3d,
184
(
185
Node3d::MainOpaquePass,
186
AtmosphereNode::RenderSky,
187
Node3d::MainTransparentPass,
188
),
189
);
190
}
191
}
192
193
/// This component describes the atmosphere of a planet, and when added to a camera
194
/// will enable atmospheric scattering for that camera. This is only compatible with
195
/// HDR cameras.
196
///
197
/// Most atmospheric particles scatter and absorb light in two main ways:
198
///
199
/// Rayleigh scattering occurs among very small particles, like individual gas
200
/// molecules. It's wavelength dependent, and causes colors to separate out as
201
/// light travels through the atmosphere. These particles *don't* absorb light.
202
///
203
/// Mie scattering occurs among slightly larger particles, like dust and sea spray.
204
/// These particles *do* absorb light, but Mie scattering and absorption is
205
/// *wavelength independent*.
206
///
207
/// Ozone acts differently from the other two, and is special-cased because
208
/// it's very important to the look of Earth's atmosphere. It's wavelength
209
/// dependent, but only *absorbs* light. Also, while the density of particles
210
/// participating in Rayleigh and Mie scattering falls off roughly exponentially
211
/// from the planet's surface, ozone only exists in a band centered at a fairly
212
/// high altitude.
213
#[derive(Clone, Component, Reflect, ShaderType)]
214
#[require(AtmosphereSettings, Hdr)]
215
#[reflect(Clone, Default)]
216
pub struct Atmosphere {
217
/// Radius of the planet
218
///
219
/// units: m
220
pub bottom_radius: f32,
221
222
/// Radius at which we consider the atmosphere to 'end' for our
223
/// calculations (from center of planet)
224
///
225
/// units: m
226
pub top_radius: f32,
227
228
/// An approximation of the average albedo (or color, roughly) of the
229
/// planet's surface. This is used when calculating multiscattering.
230
///
231
/// units: N/A
232
pub ground_albedo: Vec3,
233
234
/// The rate of falloff of rayleigh particulate with respect to altitude:
235
/// optical density = exp(-rayleigh_density_exp_scale * altitude in meters).
236
///
237
/// THIS VALUE MUST BE POSITIVE
238
///
239
/// units: N/A
240
pub rayleigh_density_exp_scale: f32,
241
242
/// The scattering optical density of rayleigh particulate, or how
243
/// much light it scatters per meter
244
///
245
/// units: m^-1
246
pub rayleigh_scattering: Vec3,
247
248
/// The rate of falloff of mie particulate with respect to altitude:
249
/// optical density = exp(-mie_density_exp_scale * altitude in meters)
250
///
251
/// THIS VALUE MUST BE POSITIVE
252
///
253
/// units: N/A
254
pub mie_density_exp_scale: f32,
255
256
/// The scattering optical density of mie particulate, or how much light
257
/// it scatters per meter.
258
///
259
/// units: m^-1
260
pub mie_scattering: f32,
261
262
/// The absorbing optical density of mie particulate, or how much light
263
/// it absorbs per meter.
264
///
265
/// units: m^-1
266
pub mie_absorption: f32,
267
268
/// The "asymmetry" of mie scattering, or how much light tends to scatter
269
/// forwards, rather than backwards or to the side.
270
///
271
/// domain: (-1, 1)
272
/// units: N/A
273
pub mie_asymmetry: f32, //the "asymmetry" value of the phase function, unitless. Domain: (-1, 1)
274
275
/// The altitude at which the ozone layer is centered.
276
///
277
/// units: m
278
pub ozone_layer_altitude: f32,
279
280
/// The width of the ozone layer
281
///
282
/// units: m
283
pub ozone_layer_width: f32,
284
285
/// The optical density of ozone, or how much of each wavelength of
286
/// light it absorbs per meter.
287
///
288
/// units: m^-1
289
pub ozone_absorption: Vec3,
290
}
291
292
impl Atmosphere {
293
pub const EARTH: Atmosphere = Atmosphere {
294
bottom_radius: 6_360_000.0,
295
top_radius: 6_460_000.0,
296
ground_albedo: Vec3::splat(0.3),
297
rayleigh_density_exp_scale: 1.0 / 8_000.0,
298
rayleigh_scattering: Vec3::new(5.802e-6, 13.558e-6, 33.100e-6),
299
mie_density_exp_scale: 1.0 / 1_200.0,
300
mie_scattering: 3.996e-6,
301
mie_absorption: 0.444e-6,
302
mie_asymmetry: 0.8,
303
ozone_layer_altitude: 25_000.0,
304
ozone_layer_width: 30_000.0,
305
ozone_absorption: Vec3::new(0.650e-6, 1.881e-6, 0.085e-6),
306
};
307
308
pub fn with_density_multiplier(mut self, mult: f32) -> Self {
309
self.rayleigh_scattering *= mult;
310
self.mie_scattering *= mult;
311
self.mie_absorption *= mult;
312
self.ozone_absorption *= mult;
313
self
314
}
315
}
316
317
impl Default for Atmosphere {
318
fn default() -> Self {
319
Self::EARTH
320
}
321
}
322
323
impl ExtractComponent for Atmosphere {
324
type QueryData = Read<Atmosphere>;
325
326
type QueryFilter = With<Camera3d>;
327
328
type Out = Atmosphere;
329
330
fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
331
Some(item.clone())
332
}
333
}
334
335
/// This component controls the resolution of the atmosphere LUTs, and
336
/// how many samples are used when computing them.
337
///
338
/// The transmittance LUT stores the transmittance from a point in the
339
/// atmosphere to the outer edge of the atmosphere in any direction,
340
/// parametrized by the point's radius and the cosine of the zenith angle
341
/// of the ray.
342
///
343
/// The multiscattering LUT stores the factor representing luminance scattered
344
/// towards the camera with scattering order >2, parametrized by the point's radius
345
/// and the cosine of the zenith angle of the sun.
346
///
347
/// The sky-view lut is essentially the actual skybox, storing the light scattered
348
/// towards the camera in every direction with a cubemap.
349
///
350
/// The aerial-view lut is a 3d LUT fit to the view frustum, which stores the luminance
351
/// scattered towards the camera at each point (RGB channels), alongside the average
352
/// transmittance to that point (A channel).
353
#[derive(Clone, Component, Reflect)]
354
#[reflect(Clone, Default)]
355
pub struct AtmosphereSettings {
356
/// The size of the transmittance LUT
357
pub transmittance_lut_size: UVec2,
358
359
/// The size of the multiscattering LUT
360
pub multiscattering_lut_size: UVec2,
361
362
/// The size of the sky-view LUT.
363
pub sky_view_lut_size: UVec2,
364
365
/// The size of the aerial-view LUT.
366
pub aerial_view_lut_size: UVec3,
367
368
/// The number of points to sample along each ray when
369
/// computing the transmittance LUT
370
pub transmittance_lut_samples: u32,
371
372
/// The number of rays to sample when computing each
373
/// pixel of the multiscattering LUT
374
pub multiscattering_lut_dirs: u32,
375
376
/// The number of points to sample when integrating along each
377
/// multiscattering ray
378
pub multiscattering_lut_samples: u32,
379
380
/// The number of points to sample along each ray when
381
/// computing the sky-view LUT.
382
pub sky_view_lut_samples: u32,
383
384
/// The number of points to sample for each slice along the z-axis
385
/// of the aerial-view LUT.
386
pub aerial_view_lut_samples: u32,
387
388
/// The maximum distance from the camera to evaluate the
389
/// aerial view LUT. The slices along the z-axis of the
390
/// texture will be distributed linearly from the camera
391
/// to this value.
392
///
393
/// units: m
394
pub aerial_view_lut_max_distance: f32,
395
396
/// A conversion factor between scene units and meters, used to
397
/// ensure correctness at different length scales.
398
pub scene_units_to_m: f32,
399
400
/// The number of points to sample for each fragment when the using
401
/// ray marching to render the sky
402
pub sky_max_samples: u32,
403
404
/// The rendering method to use for the atmosphere.
405
pub rendering_method: AtmosphereMode,
406
}
407
408
impl Default for AtmosphereSettings {
409
fn default() -> Self {
410
Self {
411
transmittance_lut_size: UVec2::new(256, 128),
412
transmittance_lut_samples: 40,
413
multiscattering_lut_size: UVec2::new(32, 32),
414
multiscattering_lut_dirs: 64,
415
multiscattering_lut_samples: 20,
416
sky_view_lut_size: UVec2::new(400, 200),
417
sky_view_lut_samples: 16,
418
aerial_view_lut_size: UVec3::new(32, 32, 32),
419
aerial_view_lut_samples: 10,
420
aerial_view_lut_max_distance: 3.2e4,
421
scene_units_to_m: 1.0,
422
sky_max_samples: 16,
423
rendering_method: AtmosphereMode::LookupTexture,
424
}
425
}
426
}
427
428
#[derive(Clone, Component, Reflect, ShaderType)]
429
#[reflect(Default)]
430
pub struct GpuAtmosphereSettings {
431
pub transmittance_lut_size: UVec2,
432
pub multiscattering_lut_size: UVec2,
433
pub sky_view_lut_size: UVec2,
434
pub aerial_view_lut_size: UVec3,
435
pub transmittance_lut_samples: u32,
436
pub multiscattering_lut_dirs: u32,
437
pub multiscattering_lut_samples: u32,
438
pub sky_view_lut_samples: u32,
439
pub aerial_view_lut_samples: u32,
440
pub aerial_view_lut_max_distance: f32,
441
pub scene_units_to_m: f32,
442
pub sky_max_samples: u32,
443
pub rendering_method: u32,
444
}
445
446
impl Default for GpuAtmosphereSettings {
447
fn default() -> Self {
448
AtmosphereSettings::default().into()
449
}
450
}
451
452
impl From<AtmosphereSettings> for GpuAtmosphereSettings {
453
fn from(s: AtmosphereSettings) -> Self {
454
Self {
455
transmittance_lut_size: s.transmittance_lut_size,
456
multiscattering_lut_size: s.multiscattering_lut_size,
457
sky_view_lut_size: s.sky_view_lut_size,
458
aerial_view_lut_size: s.aerial_view_lut_size,
459
transmittance_lut_samples: s.transmittance_lut_samples,
460
multiscattering_lut_dirs: s.multiscattering_lut_dirs,
461
multiscattering_lut_samples: s.multiscattering_lut_samples,
462
sky_view_lut_samples: s.sky_view_lut_samples,
463
aerial_view_lut_samples: s.aerial_view_lut_samples,
464
aerial_view_lut_max_distance: s.aerial_view_lut_max_distance,
465
scene_units_to_m: s.scene_units_to_m,
466
sky_max_samples: s.sky_max_samples,
467
rendering_method: s.rendering_method as u32,
468
}
469
}
470
}
471
472
impl ExtractComponent for GpuAtmosphereSettings {
473
type QueryData = Read<AtmosphereSettings>;
474
475
type QueryFilter = (With<Camera3d>, With<Atmosphere>);
476
477
type Out = GpuAtmosphereSettings;
478
479
fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
480
Some(item.clone().into())
481
}
482
}
483
484
fn configure_camera_depth_usages(
485
mut cameras: Query<&mut Camera3d, (Changed<Camera3d>, With<Atmosphere>)>,
486
) {
487
for mut camera in &mut cameras {
488
camera.depth_texture_usages.0 |= TextureUsages::TEXTURE_BINDING.bits();
489
}
490
}
491
492
/// Selects how the atmosphere is rendered. Choose based on scene scale and
493
/// volumetric shadow quality, and based on performance needs.
494
#[repr(u32)]
495
#[derive(Clone, Default, Reflect, Copy)]
496
pub enum AtmosphereMode {
497
/// High-performance solution tailored to scenes that are mostly inside of the atmosphere.
498
/// Uses a set of lookup textures to approximate scattering integration.
499
/// Slightly less accurate for very long-distance/space views (lighting precision
500
/// tapers as the camera moves far from the scene origin) and for sharp volumetric
501
/// (cloud/fog) shadows.
502
#[default]
503
LookupTexture = 0,
504
/// Slower, more accurate rendering method for any type of scene.
505
/// Integrates the scattering numerically with raymarching and produces sharp volumetric
506
/// (cloud/fog) shadows.
507
/// Best for cinematic shots, planets seen from orbit, and scenes requiring
508
/// accurate long-distance lighting.
509
Raymarched = 1,
510
}
511
512