1mod environment;
40mod node;
41pub mod resources;
42
43use bevy_app::{App, Plugin};
44use bevy_asset::{embedded_asset, AssetId};
45use bevy_camera::{Camera3d, Hdr};
46use bevy_core_pipeline::{
47 core_3d::{main_opaque_pass_3d, main_transparent_pass_3d},
48 schedule::{Core3d, Core3dSystems},
49};
50use bevy_ecs::{
51 component::Component,
52 entity::Entity,
53 query::{Changed, With},
54 schedule::IntoScheduleConfigs,
55 system::{Commands, Query},
56};
57use bevy_light::{atmosphere::ScatteringMedium, Atmosphere};
58use bevy_math::{Mat4, Quat, UVec2, UVec3, Vec3};
59use bevy_reflect::{std_traits::ReflectDefault, Reflect};
60use bevy_render::{
61 extract_component::{ExtractComponentPlugin, UniformComponentPlugin},
62 render_resource::{DownlevelFlags, ShaderType, SpecializedRenderPipelines},
63 renderer::RenderDevice,
64 sync_component::{SyncComponent, SyncComponentPlugin},
65 sync_world::RenderEntity,
66 Extract, ExtractSchedule, RenderStartup,
67};
68use bevy_render::{
69 render_resource::{TextureFormat, TextureUsages},
70 renderer::RenderAdapter,
71 GpuResourceAppExt, Render, RenderApp, RenderSystems,
72};
73use bevy_transform::components::GlobalTransform;
74
75use bevy_shader::load_shader_library;
76use environment::{
77 atmosphere_environment, init_atmosphere_probe_layout, init_atmosphere_probe_pipeline,
78 on_insert_atmosphere_environment_map_light, on_remove_atmosphere_environment_map_light,
79 prepare_atmosphere_probe_bind_groups, prepare_probe_textures, AtmosphereEnvironmentMap,
80};
81use node::{atmosphere_luts, render_sky};
82use resources::{
83 prepare_atmosphere_transforms, prepare_atmosphere_uniforms, queue_render_sky_pipelines,
84 AtmosphereTransforms, GpuAtmosphere, RenderSkyBindGroupLayouts,
85};
86use tracing::warn;
87
88use crate::resources::prepare_atmosphere_buffers;
89
90use self::resources::{
91 prepare_atmosphere_bind_groups, prepare_atmosphere_textures, AtmosphereBindGroupLayouts,
92 AtmosphereBindGroups, AtmosphereLutPipelines, AtmosphereSampler, AtmosphereTextures,
93 AtmosphereTransformsOffset, RenderSkyPipelineId,
94};
95
96#[doc(hidden)]
97pub struct AtmospherePlugin;
98
99impl Plugin for AtmospherePlugin {
100 fn build(&self, app: &mut App) {
101 load_shader_library!(app, "types.wesl");
102 load_shader_library!(app, "functions.wesl");
103 load_shader_library!(app, "bruneton_functions.wesl");
104 load_shader_library!(app, "bindings.wesl");
105
106 embedded_asset!(app, "transmittance_lut.wesl");
107 embedded_asset!(app, "multiscattering_lut.wesl");
108 embedded_asset!(app, "sky_view_lut.wesl");
109 embedded_asset!(app, "aerial_view_lut.wesl");
110 embedded_asset!(app, "render_sky.wesl");
111 embedded_asset!(app, "environment.wesl");
112
113 app.add_plugins((
114 ExtractComponentPlugin::<AtmosphereEnvironmentMap>::default(),
115 SyncComponentPlugin::<AtmosphereSettings>::default(),
116 UniformComponentPlugin::<GpuAtmosphere>::default(),
117 UniformComponentPlugin::<GpuAtmosphereSettings>::default(),
118 ))
119 .add_observer(on_insert_atmosphere_environment_map_light)
120 .add_observer(on_remove_atmosphere_environment_map_light);
121
122 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
123 render_app.add_systems(ExtractSchedule, extract_atmosphere);
124 }
125 }
126
127 fn finish(&self, app: &mut App) {
128 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
129 return;
130 };
131
132 let render_adapter = render_app.world().resource::<RenderAdapter>();
133
134 if !render_adapter
135 .get_downlevel_capabilities()
136 .flags
137 .contains(DownlevelFlags::COMPUTE_SHADERS)
138 {
139 warn!("AtmospherePlugin not loaded. GPU lacks support for compute shaders.");
140 return;
141 }
142
143 if !render_adapter
144 .get_texture_format_features(TextureFormat::Rgba16Float)
145 .allowed_usages
146 .contains(TextureUsages::STORAGE_BINDING)
147 {
148 warn!("AtmospherePlugin not loaded. GPU lacks support: TextureFormat::Rgba16Float does not support TextureUsages::STORAGE_BINDING.");
149 return;
150 }
151
152 let render_device = render_app.world().resource::<RenderDevice>();
156 if render_device.limits().max_storage_textures_per_shader_stage == 0 {
157 warn!("AtmospherePlugin not loaded. GPU lacks support: `max_storage_textures_per_shader_stage` is 0");
158 return;
159 }
160
161 render_app
162 .insert_resource(AtmosphereBindGroupLayouts::new())
163 .init_gpu_resource::<RenderSkyBindGroupLayouts>()
164 .init_gpu_resource::<AtmosphereSampler>()
165 .init_gpu_resource::<AtmosphereLutPipelines>()
166 .init_gpu_resource::<AtmosphereTransforms>()
167 .init_gpu_resource::<SpecializedRenderPipelines<RenderSkyBindGroupLayouts>>()
168 .add_systems(
169 RenderStartup,
170 (init_atmosphere_probe_layout, init_atmosphere_probe_pipeline).chain(),
171 )
172 .add_systems(
173 Render,
174 (
175 configure_camera_depth_usages.in_set(RenderSystems::PrepareViews),
176 queue_render_sky_pipelines.in_set(RenderSystems::Queue),
177 prepare_atmosphere_textures.in_set(RenderSystems::PrepareResources),
178 prepare_probe_textures
179 .in_set(RenderSystems::PrepareResources)
180 .after(prepare_atmosphere_textures),
181 prepare_atmosphere_uniforms
182 .in_set(RenderSystems::Prepare)
183 .before(RenderSystems::PrepareResources),
184 prepare_atmosphere_probe_bind_groups.in_set(RenderSystems::PrepareBindGroups),
185 prepare_atmosphere_transforms.in_set(RenderSystems::PrepareResources),
186 prepare_atmosphere_bind_groups.in_set(RenderSystems::PrepareBindGroups),
187 prepare_atmosphere_buffers.in_set(RenderSystems::PrepareResources),
188 ),
189 )
190 .add_systems(
191 Core3d,
192 (
193 (atmosphere_luts, atmosphere_environment)
194 .chain()
195 .after(Core3dSystems::Prepass)
196 .before(Core3dSystems::MainPass),
197 render_sky
198 .after(main_opaque_pass_3d)
199 .before(main_transparent_pass_3d),
200 ),
201 );
202 }
203}
204
205pub fn extract_atmosphere(
208 mut commands: Commands,
209 atmosphere_entities: Extract<Query<(Entity, &Atmosphere, &GlobalTransform)>>,
210 cameras: Extract<
211 Query<(RenderEntity, Option<&AtmosphereSettings>, &GlobalTransform), With<Camera3d>>,
212 >,
213) {
214 let candidates: Vec<(Entity, &Atmosphere, &GlobalTransform)> =
215 atmosphere_entities.iter().collect();
216
217 for (render_entity, settings, cam_global) in &cameras {
218 let (Some(settings), false) = (settings, candidates.is_empty()) else {
220 commands.entity(render_entity).try_remove::<(
221 ExtractedAtmosphere,
222 GpuAtmosphereSettings,
223 GpuAtmosphere,
224 AtmosphereTextures,
225 AtmosphereTransformsOffset,
226 RenderSkyPipelineId,
227 AtmosphereBindGroups,
228 )>();
229 continue;
230 };
231
232 let cam_world = cam_global.translation();
233 let selected = candidates
234 .iter()
235 .min_by(|(ea, _, gt_a), (eb, _, gt_b)| {
236 let da = cam_world.distance(gt_a.translation());
237 let db = cam_world.distance(gt_b.translation());
238 da.total_cmp(&db).then_with(|| ea.cmp(eb))
239 })
240 .expect("checked non-empty above");
241 let atmo = selected.1;
242 let gt = selected.2;
243 let (scale, _, translation) = gt.to_scale_rotation_translation();
245
246 let extracted = ExtractedAtmosphere {
247 inner_radius: atmo.inner_radius,
248 outer_radius: atmo.outer_radius,
249 ground_albedo: atmo.ground_albedo,
250 medium: atmo.medium.id(),
251 world_to_atmosphere: Mat4::from_scale_rotation_translation(
252 scale,
253 Quat::IDENTITY,
254 translation,
255 )
256 .inverse(),
257 };
258 commands.entity(render_entity).insert(extracted);
259 commands
260 .entity(render_entity)
261 .insert(GpuAtmosphereSettings::from(settings.clone()));
262 }
263}
264
265#[derive(Clone, Component)]
268pub struct ExtractedAtmosphere {
269 pub inner_radius: f32,
270 pub outer_radius: f32,
271 pub ground_albedo: Vec3,
272 pub medium: AssetId<ScatteringMedium>,
273 pub world_to_atmosphere: Mat4,
274}
275
276#[derive(Clone, Component, Reflect)]
295#[reflect(Clone, Default)]
296#[require(Hdr)]
297pub struct AtmosphereSettings {
298 pub transmittance_lut_size: UVec2,
300
301 pub multiscattering_lut_size: UVec2,
303
304 pub sky_view_lut_size: UVec2,
306
307 pub aerial_view_lut_size: UVec3,
309
310 pub transmittance_lut_samples: u32,
313
314 pub multiscattering_lut_dirs: u32,
317
318 pub multiscattering_lut_samples: u32,
321
322 pub sky_view_lut_samples: u32,
325
326 pub aerial_view_lut_samples: u32,
329
330 pub aerial_view_lut_max_distance: f32,
337
338 pub sky_max_samples: u32,
341
342 pub rendering_method: AtmosphereMode,
344}
345
346impl Default for AtmosphereSettings {
347 fn default() -> Self {
348 Self {
349 transmittance_lut_size: UVec2::new(256, 128),
350 transmittance_lut_samples: 40,
351 multiscattering_lut_size: UVec2::new(32, 32),
352 multiscattering_lut_dirs: 64,
353 multiscattering_lut_samples: 20,
354 sky_view_lut_size: UVec2::new(400, 200),
355 sky_view_lut_samples: 16,
356 aerial_view_lut_size: UVec3::new(32, 32, 32),
357 aerial_view_lut_samples: 10,
358 aerial_view_lut_max_distance: 3.2e4,
359 sky_max_samples: 16,
360 rendering_method: AtmosphereMode::LookupTexture,
361 }
362 }
363}
364
365#[derive(Clone, Component, Reflect, ShaderType)]
366#[reflect(Default)]
367pub struct GpuAtmosphereSettings {
368 pub transmittance_lut_size: UVec2,
369 pub multiscattering_lut_size: UVec2,
370 pub sky_view_lut_size: UVec2,
371 pub aerial_view_lut_size: UVec3,
372 pub transmittance_lut_samples: u32,
373 pub multiscattering_lut_dirs: u32,
374 pub multiscattering_lut_samples: u32,
375 pub sky_view_lut_samples: u32,
376 pub aerial_view_lut_samples: u32,
377 pub aerial_view_lut_max_distance: f32,
378 pub sky_max_samples: u32,
379 pub rendering_method: u32,
380}
381
382impl Default for GpuAtmosphereSettings {
383 fn default() -> Self {
384 AtmosphereSettings::default().into()
385 }
386}
387
388impl From<AtmosphereSettings> for GpuAtmosphereSettings {
389 fn from(s: AtmosphereSettings) -> Self {
390 Self {
391 transmittance_lut_size: s.transmittance_lut_size,
392 multiscattering_lut_size: s.multiscattering_lut_size,
393 sky_view_lut_size: s.sky_view_lut_size,
394 aerial_view_lut_size: s.aerial_view_lut_size,
395 transmittance_lut_samples: s.transmittance_lut_samples,
396 multiscattering_lut_dirs: s.multiscattering_lut_dirs,
397 multiscattering_lut_samples: s.multiscattering_lut_samples,
398 sky_view_lut_samples: s.sky_view_lut_samples,
399 aerial_view_lut_samples: s.aerial_view_lut_samples,
400 aerial_view_lut_max_distance: s.aerial_view_lut_max_distance,
401 sky_max_samples: s.sky_max_samples,
402 rendering_method: s.rendering_method as u32,
403 }
404 }
405}
406
407impl SyncComponent<RenderApp> for AtmosphereSettings {
408 type Target = GpuAtmosphereSettings;
409}
410
411fn configure_camera_depth_usages(
412 mut cameras: Query<&mut Camera3d, (Changed<Camera3d>, With<ExtractedAtmosphere>)>,
413) {
414 for mut camera in &mut cameras {
415 camera.depth_texture_usages.0 |= TextureUsages::TEXTURE_BINDING.bits();
416 }
417}
418
419#[repr(u32)]
422#[derive(Clone, Default, Reflect, Copy)]
423pub enum AtmosphereMode {
424 #[default]
430 LookupTexture = 0,
431 Raymarched = 1,
437}