Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_pbr/src/atmosphere/mod.rs
9414 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, AssetId};
42
use bevy_camera::Camera3d;
43
use bevy_core_pipeline::{
44
core_3d::{main_opaque_pass_3d, main_transparent_pass_3d},
45
schedule::{Core3d, Core3dSystems},
46
};
47
use bevy_ecs::{
48
component::Component,
49
query::{Changed, QueryItem, With},
50
schedule::IntoScheduleConfigs,
51
system::{lifetimeless::Read, Commands, Local, Query},
52
};
53
use bevy_light::{atmosphere::ScatteringMedium, Atmosphere};
54
use bevy_math::{UVec2, UVec3, Vec3};
55
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
56
use bevy_render::{
57
extract_component::UniformComponentPlugin,
58
render_resource::{DownlevelFlags, ShaderType, SpecializedRenderPipelines},
59
sync_component::SyncComponent,
60
sync_world::RenderEntity,
61
Extract, ExtractSchedule, RenderStartup,
62
};
63
use bevy_render::{
64
extract_component::{ExtractComponent, ExtractComponentPlugin},
65
render_resource::{TextureFormat, TextureUsages},
66
renderer::RenderAdapter,
67
Render, RenderApp, RenderSystems,
68
};
69
70
use bevy_shader::load_shader_library;
71
use environment::{
72
atmosphere_environment, init_atmosphere_probe_layout, init_atmosphere_probe_pipeline,
73
prepare_atmosphere_probe_bind_groups, prepare_atmosphere_probe_components,
74
prepare_probe_textures, AtmosphereEnvironmentMap,
75
};
76
use node::{atmosphere_luts, render_sky};
77
use resources::{
78
prepare_atmosphere_transforms, prepare_atmosphere_uniforms, queue_render_sky_pipelines,
79
AtmosphereTransforms, GpuAtmosphere, RenderSkyBindGroupLayouts,
80
};
81
use tracing::warn;
82
83
use crate::resources::{init_atmosphere_buffer, write_atmosphere_buffer};
84
85
use self::resources::{
86
prepare_atmosphere_bind_groups, prepare_atmosphere_textures, AtmosphereBindGroupLayouts,
87
AtmosphereLutPipelines, AtmosphereSampler,
88
};
89
90
#[doc(hidden)]
91
pub struct AtmospherePlugin;
92
93
impl Plugin for AtmospherePlugin {
94
fn build(&self, app: &mut App) {
95
load_shader_library!(app, "types.wgsl");
96
load_shader_library!(app, "functions.wgsl");
97
load_shader_library!(app, "bruneton_functions.wgsl");
98
load_shader_library!(app, "bindings.wgsl");
99
100
embedded_asset!(app, "transmittance_lut.wgsl");
101
embedded_asset!(app, "multiscattering_lut.wgsl");
102
embedded_asset!(app, "sky_view_lut.wgsl");
103
embedded_asset!(app, "aerial_view_lut.wgsl");
104
embedded_asset!(app, "render_sky.wgsl");
105
embedded_asset!(app, "environment.wgsl");
106
107
app.add_plugins((
108
ExtractComponentPlugin::<GpuAtmosphereSettings>::default(),
109
ExtractComponentPlugin::<AtmosphereEnvironmentMap>::default(),
110
UniformComponentPlugin::<GpuAtmosphere>::default(),
111
UniformComponentPlugin::<GpuAtmosphereSettings>::default(),
112
))
113
.register_required_components::<Atmosphere, AtmosphereSettings>()
114
.add_systems(Update, prepare_atmosphere_probe_components);
115
116
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
117
render_app.add_systems(ExtractSchedule, extract_atmosphere);
118
}
119
}
120
121
fn finish(&self, app: &mut App) {
122
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
123
return;
124
};
125
126
let render_adapter = render_app.world().resource::<RenderAdapter>();
127
128
if !render_adapter
129
.get_downlevel_capabilities()
130
.flags
131
.contains(DownlevelFlags::COMPUTE_SHADERS)
132
{
133
warn!("AtmospherePlugin not loaded. GPU lacks support for compute shaders.");
134
return;
135
}
136
137
if !render_adapter
138
.get_texture_format_features(TextureFormat::Rgba16Float)
139
.allowed_usages
140
.contains(TextureUsages::STORAGE_BINDING)
141
{
142
warn!("AtmospherePlugin not loaded. GPU lacks support: TextureFormat::Rgba16Float does not support TextureUsages::STORAGE_BINDING.");
143
return;
144
}
145
146
render_app
147
.insert_resource(AtmosphereBindGroupLayouts::new())
148
.init_resource::<RenderSkyBindGroupLayouts>()
149
.init_resource::<AtmosphereSampler>()
150
.init_resource::<AtmosphereLutPipelines>()
151
.init_resource::<AtmosphereTransforms>()
152
.init_resource::<SpecializedRenderPipelines<RenderSkyBindGroupLayouts>>()
153
.add_systems(
154
RenderStartup,
155
(
156
init_atmosphere_probe_layout,
157
init_atmosphere_probe_pipeline,
158
init_atmosphere_buffer,
159
)
160
.chain(),
161
)
162
.add_systems(
163
Render,
164
(
165
configure_camera_depth_usages.in_set(RenderSystems::ManageViews),
166
queue_render_sky_pipelines.in_set(RenderSystems::Queue),
167
prepare_atmosphere_textures.in_set(RenderSystems::PrepareResources),
168
prepare_probe_textures
169
.in_set(RenderSystems::PrepareResources)
170
.after(prepare_atmosphere_textures),
171
prepare_atmosphere_uniforms
172
.before(RenderSystems::PrepareResources)
173
.after(RenderSystems::PrepareAssets),
174
prepare_atmosphere_probe_bind_groups.in_set(RenderSystems::PrepareBindGroups),
175
prepare_atmosphere_transforms.in_set(RenderSystems::PrepareResources),
176
prepare_atmosphere_bind_groups.in_set(RenderSystems::PrepareBindGroups),
177
write_atmosphere_buffer.in_set(RenderSystems::PrepareResources),
178
),
179
)
180
.add_systems(
181
Core3d,
182
(
183
(atmosphere_luts, atmosphere_environment)
184
.chain()
185
.after(Core3dSystems::Prepass)
186
.before(Core3dSystems::MainPass),
187
render_sky
188
.after(main_opaque_pass_3d)
189
.before(main_transparent_pass_3d),
190
),
191
);
192
}
193
}
194
195
// This is needed because of the orphan rule not allowing implementing
196
// foreign trait ExtractComponent on foreign type Atmosphere
197
pub fn extract_atmosphere(
198
mut commands: Commands,
199
mut previous_len: Local<usize>,
200
query: Extract<Query<(RenderEntity, &Atmosphere), With<Camera3d>>>,
201
) {
202
let mut values = Vec::with_capacity(*previous_len);
203
for (entity, item) in &query {
204
values.push((
205
entity,
206
ExtractedAtmosphere {
207
bottom_radius: item.bottom_radius,
208
top_radius: item.top_radius,
209
ground_albedo: item.ground_albedo,
210
medium: item.medium.id(),
211
},
212
));
213
}
214
*previous_len = values.len();
215
commands.try_insert_batch(values);
216
}
217
218
/// The render-world representation of an `Atmosphere`, but which
219
/// hasn't been converted into shader uniforms yet.
220
#[derive(Clone, Component)]
221
pub struct ExtractedAtmosphere {
222
pub bottom_radius: f32,
223
pub top_radius: f32,
224
pub ground_albedo: Vec3,
225
pub medium: AssetId<ScatteringMedium>,
226
}
227
228
/// This component controls the resolution of the atmosphere LUTs, and
229
/// how many samples are used when computing them.
230
///
231
/// The transmittance LUT stores the transmittance from a point in the
232
/// atmosphere to the outer edge of the atmosphere in any direction,
233
/// parametrized by the point's radius and the cosine of the zenith angle
234
/// of the ray.
235
///
236
/// The multiscattering LUT stores the factor representing luminance scattered
237
/// towards the camera with scattering order >2, parametrized by the point's radius
238
/// and the cosine of the zenith angle of the sun.
239
///
240
/// The sky-view lut is essentially the actual skybox, storing the light scattered
241
/// towards the camera in every direction with a cubemap.
242
///
243
/// The aerial-view lut is a 3d LUT fit to the view frustum, which stores the luminance
244
/// scattered towards the camera at each point (RGB channels), alongside the average
245
/// transmittance to that point (A channel).
246
#[derive(Clone, Component, Reflect)]
247
#[reflect(Clone, Default)]
248
pub struct AtmosphereSettings {
249
/// The size of the transmittance LUT
250
pub transmittance_lut_size: UVec2,
251
252
/// The size of the multiscattering LUT
253
pub multiscattering_lut_size: UVec2,
254
255
/// The size of the sky-view LUT.
256
pub sky_view_lut_size: UVec2,
257
258
/// The size of the aerial-view LUT.
259
pub aerial_view_lut_size: UVec3,
260
261
/// The number of points to sample along each ray when
262
/// computing the transmittance LUT
263
pub transmittance_lut_samples: u32,
264
265
/// The number of rays to sample when computing each
266
/// pixel of the multiscattering LUT
267
pub multiscattering_lut_dirs: u32,
268
269
/// The number of points to sample when integrating along each
270
/// multiscattering ray
271
pub multiscattering_lut_samples: u32,
272
273
/// The number of points to sample along each ray when
274
/// computing the sky-view LUT.
275
pub sky_view_lut_samples: u32,
276
277
/// The number of points to sample for each slice along the z-axis
278
/// of the aerial-view LUT.
279
pub aerial_view_lut_samples: u32,
280
281
/// The maximum distance from the camera to evaluate the
282
/// aerial view LUT. The slices along the z-axis of the
283
/// texture will be distributed linearly from the camera
284
/// to this value.
285
///
286
/// units: m
287
pub aerial_view_lut_max_distance: f32,
288
289
/// A conversion factor between scene units and meters, used to
290
/// ensure correctness at different length scales.
291
pub scene_units_to_m: f32,
292
293
/// The number of points to sample for each fragment when the using
294
/// ray marching to render the sky
295
pub sky_max_samples: u32,
296
297
/// The rendering method to use for the atmosphere.
298
pub rendering_method: AtmosphereMode,
299
}
300
301
impl Default for AtmosphereSettings {
302
fn default() -> Self {
303
Self {
304
transmittance_lut_size: UVec2::new(256, 128),
305
transmittance_lut_samples: 40,
306
multiscattering_lut_size: UVec2::new(32, 32),
307
multiscattering_lut_dirs: 64,
308
multiscattering_lut_samples: 20,
309
sky_view_lut_size: UVec2::new(400, 200),
310
sky_view_lut_samples: 16,
311
aerial_view_lut_size: UVec3::new(32, 32, 32),
312
aerial_view_lut_samples: 10,
313
aerial_view_lut_max_distance: 3.2e4,
314
scene_units_to_m: 1.0,
315
sky_max_samples: 16,
316
rendering_method: AtmosphereMode::LookupTexture,
317
}
318
}
319
}
320
321
#[derive(Clone, Component, Reflect, ShaderType)]
322
#[reflect(Default)]
323
pub struct GpuAtmosphereSettings {
324
pub transmittance_lut_size: UVec2,
325
pub multiscattering_lut_size: UVec2,
326
pub sky_view_lut_size: UVec2,
327
pub aerial_view_lut_size: UVec3,
328
pub transmittance_lut_samples: u32,
329
pub multiscattering_lut_dirs: u32,
330
pub multiscattering_lut_samples: u32,
331
pub sky_view_lut_samples: u32,
332
pub aerial_view_lut_samples: u32,
333
pub aerial_view_lut_max_distance: f32,
334
pub scene_units_to_m: f32,
335
pub sky_max_samples: u32,
336
pub rendering_method: u32,
337
}
338
339
impl Default for GpuAtmosphereSettings {
340
fn default() -> Self {
341
AtmosphereSettings::default().into()
342
}
343
}
344
345
impl From<AtmosphereSettings> for GpuAtmosphereSettings {
346
fn from(s: AtmosphereSettings) -> Self {
347
Self {
348
transmittance_lut_size: s.transmittance_lut_size,
349
multiscattering_lut_size: s.multiscattering_lut_size,
350
sky_view_lut_size: s.sky_view_lut_size,
351
aerial_view_lut_size: s.aerial_view_lut_size,
352
transmittance_lut_samples: s.transmittance_lut_samples,
353
multiscattering_lut_dirs: s.multiscattering_lut_dirs,
354
multiscattering_lut_samples: s.multiscattering_lut_samples,
355
sky_view_lut_samples: s.sky_view_lut_samples,
356
aerial_view_lut_samples: s.aerial_view_lut_samples,
357
aerial_view_lut_max_distance: s.aerial_view_lut_max_distance,
358
scene_units_to_m: s.scene_units_to_m,
359
sky_max_samples: s.sky_max_samples,
360
rendering_method: s.rendering_method as u32,
361
}
362
}
363
}
364
365
impl SyncComponent for GpuAtmosphereSettings {
366
type Out = Self;
367
}
368
369
impl ExtractComponent for GpuAtmosphereSettings {
370
type QueryData = Read<AtmosphereSettings>;
371
type QueryFilter = (With<Camera3d>, With<Atmosphere>);
372
373
fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
374
Some(item.clone().into())
375
}
376
}
377
378
fn configure_camera_depth_usages(
379
mut cameras: Query<&mut Camera3d, (Changed<Camera3d>, With<ExtractedAtmosphere>)>,
380
) {
381
for mut camera in &mut cameras {
382
camera.depth_texture_usages.0 |= TextureUsages::TEXTURE_BINDING.bits();
383
}
384
}
385
386
/// Selects how the atmosphere is rendered. Choose based on scene scale and
387
/// volumetric shadow quality, and based on performance needs.
388
#[repr(u32)]
389
#[derive(Clone, Default, Reflect, Copy)]
390
pub enum AtmosphereMode {
391
/// High-performance solution tailored to scenes that are mostly inside of the atmosphere.
392
/// Uses a set of lookup textures to approximate scattering integration.
393
/// Slightly less accurate for very long-distance/space views (lighting precision
394
/// tapers as the camera moves far from the scene origin) and for sharp volumetric
395
/// (cloud/fog) shadows.
396
#[default]
397
LookupTexture = 0,
398
/// Slower, more accurate rendering method for any type of scene.
399
/// Integrates the scattering numerically with raymarching and produces sharp volumetric
400
/// (cloud/fog) shadows.
401
/// Best for cinematic shots, planets seen from orbit, and scenes requiring
402
/// accurate long-distance lighting.
403
Raymarched = 1,
404
}
405
406