Skip to content

Commit 16a6a96

Browse files
ecoskeymate-h
andauthored
Generalized atmospheric scattering media (#20838)
# Objective Right now, only a very small set of atmospheres are possible with Bevy's atmospheric scattering system because of the fixed set of scattering terms available. For example, a scene set in a dry, desert environment might want a dense low-lying layer of dust particulate separate from the ambient dust in the atmosphere. This PR introduces a mechanism for generalized scattering media, replacing the fixed scattering terms with a customizable asset. ## Solution ```rust #[derive(TypePath, Asset, Clone)] pub struct ScatteringMedium { pub label: Option<Cow<'static, str>>, pub falloff_resolution: u32, pub phase_resolution: u32, pub terms: SmallVec<[ScatteringTerm; 1]>, } ``` <details> <summary>see other new types</summary> ```rust #[derive(Default, Clone)] pub struct ScatteringTerm { pub absorption: Vec3, pub scattering: Vec3, pub falloff: Falloff, pub phase: PhaseFunction, } #[derive(Default, Clone)] pub enum Falloff { #[default] Linear, Exponential { scale: f32, }, Tent { center: f32, width: f32, }, /// A falloff function defined by a custom curve. /// /// domain: [0, 1), /// range: [0, 1], Curve(Arc<dyn Curve<f32> + Send + Sync>), } #[derive(Clone)] pub enum PhaseFunction { Isotropic, Rayleigh, Mie { /// domain: [-1, 1] bias: f32, }, /// A phase function defined by a custom curve. /// /// domain: [-1, 1] /// range: [0, 1] Curve(Arc<dyn Curve<f32> + Send + Sync>), } ``` </details> `ScatteringMedium` contains a list of `ScatteringTerms`, which are processed into a set of two LUTs: - The "density LUT", a 2D `falloff_resolution x 2` LUT which contains the medium's optical density with respect to the atmosphere's "falloff parameter", a linear value which is 1.0 at the planet's surface and 0.0 at the edge of space. Absorption density and scattering density correspond to the first and second rows respectively. - The "scattering LUT", a 2D `falloff_resolution x phase_resolution` LUT which contains the medium's scattering density multiplied by the phase function, with the U axis corresponding to the falloff parameter and the V axis corresponding to `neg_LdotV * 0.5 + 0.5`, where `neg_LdotV` is the dot product of the light direction and the outgoing view vector. ## Testing - Need to verify output, should be almost exactly the same - exponential falloff is slightly different now, but verified new parameters against the old in Desmos. ## TODOS: - Docs Docs Docs - Cleanup - profile perf - reduce memory usage/traffic. This approach requires a few extra texture samples in the inner loop of each pass. Each atmosphere LUT is still quite small, but texture samples are expensive and the new LUTs use f32 texels currently. ## Showcase <details> <summary>Click to view showcase</summary> ```rust fn init_atmosphere(mut commands: Commands, scattering_media: ResMut<Assets<ScatteringMedium>>) { let earth_atmosphere = scattering_media.add( ScatteringMedium::new( 256, 256, [ // rayleigh scattering ScatteringTerm { absorption: Vec3::ZERO, scattering: Vec3::new(5.802e-6, 13.558e-6, 33.100e-6), falloff: Falloff::Exponential { scale: 12.5 }, phase: PhaseFunction::Rayleigh, }, // mie scattering ScatteringTerm { absorption: Vec3::splat(3.996e-6), scattering: Vec3::splat(0.444e-6), falloff: Falloff::Exponential { scale: 83.5 }, phase: PhaseFunction::Mie { bias: 0.8 }, }, // ozone ScatteringTerm { absorption: Vec3::new(0.650e-6, 1.881e-6, 0.085e-6), scattering: Vec3::ZERO, falloff: Falloff::Tent { center: 0.75, width: 0.3, }, phase: PhaseFunction::Isotropic, }, ], )); commands.spawn(( Camera3d::default(), Atmosphere { bottom_radius: 6_360_000.0, top_radius: 6_460_000.0, ground_albedo: Vec3::splat(0.3), medium: earth_atmosphere, }, )); } ``` </details> --------- Co-authored-by: Máté Homolya <[email protected]>
1 parent d5a16b7 commit 16a6a96

File tree

18 files changed

+1078
-398
lines changed

18 files changed

+1078
-398
lines changed

crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44
types::{Atmosphere, AtmosphereSettings},
55
bindings::{atmosphere, settings, view, lights, aerial_view_lut_out},
66
functions::{
7-
sample_transmittance_lut, sample_atmosphere, rayleigh, henyey_greenstein,
7+
sample_transmittance_lut, sample_density_lut, rayleigh, henyey_greenstein,
88
sample_multiscattering_lut, AtmosphereSample, sample_local_inscattering,
99
uv_to_ndc, max_atmosphere_distance, uv_to_ray_direction,
10-
MIDPOINT_RATIO, get_view_position
10+
MIDPOINT_RATIO, get_view_position, MIN_EXTINCTION, ABSORPTION_DENSITY,
11+
SCATTERING_DENSITY,
1112
},
1213
}
1314
}
1415

1516

16-
@group(0) @binding(13) var aerial_view_lut_out: texture_storage_3d<rgba16float, write>;
17+
@group(0) @binding(16) var aerial_view_lut_out: texture_storage_3d<rgba16float, write>;
1718

1819
@compute
1920
@workgroup_size(16, 16, 1)
@@ -23,7 +24,7 @@ fn main(@builtin(global_invocation_id) idx: vec3<u32>) {
2324
let uv = (vec2<f32>(idx.xy) + 0.5) / vec2<f32>(settings.aerial_view_lut_size.xy);
2425
let ray_dir = uv_to_ray_direction(uv);
2526
let world_pos = get_view_position();
26-
27+
2728
let r = length(world_pos);
2829
let t_max = settings.aerial_view_lut_max_distance;
2930

@@ -41,15 +42,18 @@ fn main(@builtin(global_invocation_id) idx: vec3<u32>) {
4142
let local_r = length(sample_pos);
4243
let local_up = normalize(sample_pos);
4344

44-
let local_atmosphere = sample_atmosphere(local_r);
45-
let sample_optical_depth = local_atmosphere.extinction * dt;
45+
let absorption = sample_density_lut(local_r, ABSORPTION_DENSITY);
46+
let scattering = sample_density_lut(local_r, SCATTERING_DENSITY);
47+
let extinction = absorption + scattering;
48+
49+
let sample_optical_depth = extinction * dt;
4650
let sample_transmittance = exp(-sample_optical_depth);
4751

4852
// evaluate one segment of the integral
49-
var inscattering = sample_local_inscattering(local_atmosphere, ray_dir, sample_pos);
53+
var inscattering = sample_local_inscattering(scattering, ray_dir, sample_pos);
5054

5155
// Analytical integration of the single scattering term in the radiance transfer equation
52-
let s_int = (inscattering - inscattering * sample_transmittance) / local_atmosphere.extinction;
56+
let s_int = (inscattering - inscattering * sample_transmittance) / max(extinction, MIN_EXTINCTION);
5357
total_inscattering += throughput * s_int;
5458

5559
throughput *= sample_transmittance;

crates/bevy_pbr/src/atmosphere/bindings.wgsl

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@
1212
@group(0) @binding(2) var<uniform> atmosphere_transforms: AtmosphereTransforms;
1313
@group(0) @binding(3) var<uniform> view: View;
1414
@group(0) @binding(4) var<uniform> lights: Lights;
15-
@group(0) @binding(5) var transmittance_lut: texture_2d<f32>;
16-
@group(0) @binding(6) var transmittance_lut_sampler: sampler;
17-
@group(0) @binding(7) var multiscattering_lut: texture_2d<f32>;
18-
@group(0) @binding(8) var multiscattering_lut_sampler: sampler;
19-
@group(0) @binding(9) var sky_view_lut: texture_2d<f32>;
20-
@group(0) @binding(10) var sky_view_lut_sampler: sampler;
21-
@group(0) @binding(11) var aerial_view_lut: texture_3d<f32>;
22-
@group(0) @binding(12) var aerial_view_lut_sampler: sampler;
15+
16+
@group(0) @binding(5) var medium_density_lut: texture_2d<f32>;
17+
@group(0) @binding(6) var medium_scattering_lut: texture_2d<f32>;
18+
@group(0) @binding(7) var medium_sampler: sampler;
19+
20+
@group(0) @binding(8) var transmittance_lut: texture_2d<f32>;
21+
@group(0) @binding(9) var transmittance_lut_sampler: sampler;
22+
@group(0) @binding(10) var multiscattering_lut: texture_2d<f32>;
23+
@group(0) @binding(11) var multiscattering_lut_sampler: sampler;
24+
@group(0) @binding(12) var sky_view_lut: texture_2d<f32>;
25+
@group(0) @binding(13) var sky_view_lut_sampler: sampler;
26+
@group(0) @binding(14) var aerial_view_lut: texture_3d<f32>;
27+
@group(0) @binding(15) var aerial_view_lut_sampler: sampler;

crates/bevy_pbr/src/atmosphere/environment.rs

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::{
22
resources::{
33
AtmosphereSamplers, AtmosphereTextures, AtmosphereTransform, AtmosphereTransforms,
4-
AtmosphereTransformsOffset,
4+
AtmosphereTransformsOffset, GpuAtmosphere,
55
},
6-
GpuAtmosphereSettings, GpuLights, LightMeta, ViewLightsUniformOffset,
6+
ExtractedAtmosphere, GpuAtmosphereSettings, GpuLights, LightMeta, ViewLightsUniformOffset,
77
};
88
use bevy_asset::{load_embedded_asset, AssetServer, Assets, Handle, RenderAssetUsages};
99
use bevy_ecs::{
@@ -29,8 +29,6 @@ use bevy_render::{
2929
use bevy_utils::default;
3030
use tracing::warn;
3131

32-
use super::Atmosphere;
33-
3432
// Render world representation of an environment map light for the atmosphere
3533
#[derive(Component, ExtractComponent, Clone)]
3634
pub struct AtmosphereEnvironmentMap {
@@ -65,26 +63,33 @@ pub struct AtmosphereProbePipeline {
6563
pub fn init_atmosphere_probe_layout(mut commands: Commands) {
6664
let environment = BindGroupLayoutDescriptor::new(
6765
"environment_bind_group_layout",
68-
&BindGroupLayoutEntries::sequential(
66+
&BindGroupLayoutEntries::with_indices(
6967
ShaderStages::COMPUTE,
7068
(
71-
uniform_buffer::<Atmosphere>(true),
72-
uniform_buffer::<GpuAtmosphereSettings>(true),
73-
uniform_buffer::<AtmosphereTransform>(true),
74-
uniform_buffer::<ViewUniform>(true),
75-
uniform_buffer::<GpuLights>(true),
76-
texture_2d(TextureSampleType::Float { filterable: true }), //transmittance lut and sampler
77-
sampler(SamplerBindingType::Filtering),
78-
texture_2d(TextureSampleType::Float { filterable: true }), //multiscattering lut and sampler
79-
sampler(SamplerBindingType::Filtering),
80-
texture_2d(TextureSampleType::Float { filterable: true }), //sky view lut and sampler
81-
sampler(SamplerBindingType::Filtering),
82-
texture_3d(TextureSampleType::Float { filterable: true }), //aerial view lut ans sampler
83-
sampler(SamplerBindingType::Filtering),
84-
texture_storage_2d_array(
85-
// output 2D array texture
86-
TextureFormat::Rgba16Float,
87-
StorageTextureAccess::WriteOnly,
69+
(0, uniform_buffer::<GpuAtmosphere>(true)),
70+
(1, uniform_buffer::<GpuAtmosphereSettings>(true)),
71+
(2, uniform_buffer::<AtmosphereTransform>(true)),
72+
(3, uniform_buffer::<ViewUniform>(true)),
73+
(4, uniform_buffer::<GpuLights>(true)),
74+
//transmittance lut and sampler
75+
(8, texture_2d(TextureSampleType::default())),
76+
(9, sampler(SamplerBindingType::Filtering)),
77+
//multiscattering lut and sampler
78+
(10, texture_2d(TextureSampleType::default())),
79+
(11, sampler(SamplerBindingType::Filtering)),
80+
//sky view lut and sampler
81+
(12, texture_2d(TextureSampleType::default())),
82+
(13, sampler(SamplerBindingType::Filtering)),
83+
//aerial view lut ans sampler
84+
(14, texture_3d(TextureSampleType::default())),
85+
(15, sampler(SamplerBindingType::Filtering)),
86+
// output 2D array texture
87+
(
88+
16,
89+
texture_storage_2d_array(
90+
TextureFormat::Rgba16Float,
91+
StorageTextureAccess::WriteOnly,
92+
),
8893
),
8994
),
9095
),
@@ -101,7 +106,7 @@ pub(super) fn prepare_atmosphere_probe_bind_groups(
101106
view_uniforms: Res<ViewUniforms>,
102107
lights_uniforms: Res<LightMeta>,
103108
atmosphere_transforms: Res<AtmosphereTransforms>,
104-
atmosphere_uniforms: Res<ComponentUniforms<Atmosphere>>,
109+
atmosphere_uniforms: Res<ComponentUniforms<GpuAtmosphere>>,
105110
settings_uniforms: Res<ComponentUniforms<GpuAtmosphereSettings>>,
106111
pipeline_cache: Res<PipelineCache>,
107112
mut commands: Commands,
@@ -110,21 +115,21 @@ pub(super) fn prepare_atmosphere_probe_bind_groups(
110115
let environment = render_device.create_bind_group(
111116
"environment_bind_group",
112117
&pipeline_cache.get_bind_group_layout(&layouts.environment),
113-
&BindGroupEntries::sequential((
114-
atmosphere_uniforms.binding().unwrap(),
115-
settings_uniforms.binding().unwrap(),
116-
atmosphere_transforms.uniforms().binding().unwrap(),
117-
view_uniforms.uniforms.binding().unwrap(),
118-
lights_uniforms.view_gpu_lights.binding().unwrap(),
119-
&textures.transmittance_lut.default_view,
120-
&samplers.transmittance_lut,
121-
&textures.multiscattering_lut.default_view,
122-
&samplers.multiscattering_lut,
123-
&textures.sky_view_lut.default_view,
124-
&samplers.sky_view_lut,
125-
&textures.aerial_view_lut.default_view,
126-
&samplers.aerial_view_lut,
127-
&textures.environment,
118+
&BindGroupEntries::with_indices((
119+
(0, atmosphere_uniforms.binding().unwrap()),
120+
(1, settings_uniforms.binding().unwrap()),
121+
(2, atmosphere_transforms.uniforms().binding().unwrap()),
122+
(3, view_uniforms.uniforms.binding().unwrap()),
123+
(4, lights_uniforms.view_gpu_lights.binding().unwrap()),
124+
(8, &textures.transmittance_lut.default_view),
125+
(9, &samplers.transmittance_lut),
126+
(10, &textures.multiscattering_lut.default_view),
127+
(11, &samplers.multiscattering_lut),
128+
(12, &textures.sky_view_lut.default_view),
129+
(13, &samplers.sky_view_lut),
130+
(14, &textures.aerial_view_lut.default_view),
131+
(15, &samplers.aerial_view_lut),
132+
(16, &textures.environment),
128133
)),
129134
);
130135

@@ -135,7 +140,7 @@ pub(super) fn prepare_atmosphere_probe_bind_groups(
135140
}
136141

137142
pub(super) fn prepare_probe_textures(
138-
view_textures: Query<&AtmosphereTextures, With<Atmosphere>>,
143+
view_textures: Query<&AtmosphereTextures, With<ExtractedAtmosphere>>,
139144
probes: Query<
140145
(Entity, &AtmosphereEnvironmentMap),
141146
(
@@ -246,7 +251,7 @@ pub fn prepare_atmosphere_probe_components(
246251

247252
pub(super) struct EnvironmentNode {
248253
main_view_query: QueryState<(
249-
Read<DynamicUniformIndex<Atmosphere>>,
254+
Read<DynamicUniformIndex<GpuAtmosphere>>,
250255
Read<DynamicUniformIndex<GpuAtmosphereSettings>>,
251256
Read<AtmosphereTransformsOffset>,
252257
Read<ViewUniformOffset>,

crates/bevy_pbr/src/atmosphere/environment.wgsl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
utils::sample_cube_dir
66
}
77

8-
@group(0) @binding(13) var output: texture_storage_2d_array<rgba16float, write>;
8+
@group(0) @binding(16) var output: texture_storage_2d_array<rgba16float, write>;
99

1010
@compute @workgroup_size(8, 8, 1)
1111
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
@@ -36,4 +36,4 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
3636
let color = vec4<f32>(inscattering, 1.0);
3737

3838
textureStore(output, vec2<i32>(global_id.xy), i32(slice_index), color);
39-
}
39+
}

0 commit comments

Comments
 (0)