1mod environment;
40mod node;
41pub mod resources;
42
43use bevy_app::{App, Plugin, Update};
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, 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 prepare_atmosphere_probe_bind_groups, prepare_atmosphere_probe_components,
79 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::{init_atmosphere_buffer, write_atmosphere_buffer};
89
90use self::resources::{
91 prepare_atmosphere_bind_groups, prepare_atmosphere_textures, AtmosphereBindGroupLayouts,
92 AtmosphereLutPipelines, AtmosphereSampler,
93};
94
95#[doc(hidden)]
96pub struct AtmospherePlugin;
97
98impl Plugin for AtmospherePlugin {
99 fn build(&self, app: &mut App) {
100 load_shader_library!(app, "types.wgsl");
101 load_shader_library!(app, "functions.wgsl");
102 load_shader_library!(app, "bruneton_functions.wgsl");
103 load_shader_library!(app, "bindings.wgsl");
104
105 embedded_asset!(app, "transmittance_lut.wgsl");
106 embedded_asset!(app, "multiscattering_lut.wgsl");
107 embedded_asset!(app, "sky_view_lut.wgsl");
108 embedded_asset!(app, "aerial_view_lut.wgsl");
109 embedded_asset!(app, "render_sky.wgsl");
110 embedded_asset!(app, "environment.wgsl");
111
112 app.add_plugins((
113 ExtractComponentPlugin::<AtmosphereEnvironmentMap>::default(),
114 SyncComponentPlugin::<AtmosphereSettings>::default(),
115 UniformComponentPlugin::<GpuAtmosphere>::default(),
116 UniformComponentPlugin::<GpuAtmosphereSettings>::default(),
117 ))
118 .add_systems(Update, prepare_atmosphere_probe_components);
119
120 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
121 render_app.add_systems(ExtractSchedule, extract_atmosphere);
122 }
123 }
124
125 fn finish(&self, app: &mut App) {
126 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
127 return;
128 };
129
130 let render_adapter = render_app.world().resource::<RenderAdapter>();
131
132 if !render_adapter
133 .get_downlevel_capabilities()
134 .flags
135 .contains(DownlevelFlags::COMPUTE_SHADERS)
136 {
137 warn!("AtmospherePlugin not loaded. GPU lacks support for compute shaders.");
138 return;
139 }
140
141 if !render_adapter
142 .get_texture_format_features(TextureFormat::Rgba16Float)
143 .allowed_usages
144 .contains(TextureUsages::STORAGE_BINDING)
145 {
146 warn!("AtmospherePlugin not loaded. GPU lacks support: TextureFormat::Rgba16Float does not support TextureUsages::STORAGE_BINDING.");
147 return;
148 }
149
150 let render_device = render_app.world().resource::<RenderDevice>();
154 if render_device.limits().max_storage_textures_per_shader_stage == 0 {
155 warn!("AtmospherePlugin not loaded. GPU lacks support: `max_storage_textures_per_shader_stage` is 0");
156 return;
157 }
158
159 render_app
160 .insert_resource(AtmosphereBindGroupLayouts::new())
161 .init_gpu_resource::<RenderSkyBindGroupLayouts>()
162 .init_gpu_resource::<AtmosphereSampler>()
163 .init_gpu_resource::<AtmosphereLutPipelines>()
164 .init_gpu_resource::<AtmosphereTransforms>()
165 .init_gpu_resource::<SpecializedRenderPipelines<RenderSkyBindGroupLayouts>>()
166 .add_systems(
167 RenderStartup,
168 (
169 init_atmosphere_probe_layout,
170 init_atmosphere_probe_pipeline,
171 init_atmosphere_buffer,
172 )
173 .chain(),
174 )
175 .add_systems(
176 Render,
177 (
178 configure_camera_depth_usages.in_set(RenderSystems::PrepareViews),
179 queue_render_sky_pipelines.in_set(RenderSystems::Queue),
180 prepare_atmosphere_textures.in_set(RenderSystems::PrepareResources),
181 prepare_probe_textures
182 .in_set(RenderSystems::PrepareResources)
183 .after(prepare_atmosphere_textures),
184 prepare_atmosphere_uniforms
185 .in_set(RenderSystems::Prepare)
186 .before(RenderSystems::PrepareResources),
187 prepare_atmosphere_probe_bind_groups.in_set(RenderSystems::PrepareBindGroups),
188 prepare_atmosphere_transforms.in_set(RenderSystems::PrepareResources),
189 prepare_atmosphere_bind_groups.in_set(RenderSystems::PrepareBindGroups),
190 write_atmosphere_buffer.in_set(RenderSystems::PrepareResources),
191 ),
192 )
193 .add_systems(
194 Core3d,
195 (
196 (atmosphere_luts, atmosphere_environment)
197 .chain()
198 .after(Core3dSystems::Prepass)
199 .before(Core3dSystems::MainPass),
200 render_sky
201 .after(main_opaque_pass_3d)
202 .before(main_transparent_pass_3d),
203 ),
204 );
205 }
206}
207
208pub fn extract_atmosphere(
211 mut commands: Commands,
212 atmosphere_entities: Extract<Query<(Entity, &Atmosphere, &GlobalTransform)>>,
213 cameras: Extract<Query<(RenderEntity, &AtmosphereSettings, &GlobalTransform), With<Camera3d>>>,
214) {
215 let candidates: Vec<(Entity, &Atmosphere, &GlobalTransform)> =
216 atmosphere_entities.iter().collect();
217
218 if candidates.is_empty() {
219 for (render_entity, ..) in &cameras {
220 commands
221 .entity(render_entity)
222 .remove::<ExtractedAtmosphere>();
223 commands
224 .entity(render_entity)
225 .remove::<GpuAtmosphereSettings>();
226 }
227 return;
228 }
229
230 for (render_entity, settings, cam_global) in &cameras {
231 let cam_world = cam_global.translation();
232 let selected = candidates
233 .iter()
234 .min_by(|(ea, _, gt_a), (eb, _, gt_b)| {
235 let da = cam_world.distance(gt_a.translation());
236 let db = cam_world.distance(gt_b.translation());
237 da.total_cmp(&db).then_with(|| ea.cmp(eb))
238 })
239 .expect("checked non-empty above");
240 let atmo = selected.1;
241 let gt = selected.2;
242
243 let extracted = ExtractedAtmosphere {
244 inner_radius: atmo.inner_radius,
245 outer_radius: atmo.outer_radius,
246 ground_albedo: atmo.ground_albedo,
247 medium: atmo.medium.id(),
248 world_to_atmosphere: gt.to_matrix().inverse(),
249 };
250 commands.entity(render_entity).insert(extracted);
251 commands
252 .entity(render_entity)
253 .insert(GpuAtmosphereSettings::from(settings.clone()));
254 }
255}
256
257#[derive(Clone, Component)]
260pub struct ExtractedAtmosphere {
261 pub inner_radius: f32,
262 pub outer_radius: f32,
263 pub ground_albedo: Vec3,
264 pub medium: AssetId<ScatteringMedium>,
265 pub world_to_atmosphere: Mat4,
266}
267
268#[derive(Clone, Component, Reflect)]
287#[reflect(Clone, Default)]
288#[require(Hdr)]
289pub struct AtmosphereSettings {
290 pub transmittance_lut_size: UVec2,
292
293 pub multiscattering_lut_size: UVec2,
295
296 pub sky_view_lut_size: UVec2,
298
299 pub aerial_view_lut_size: UVec3,
301
302 pub transmittance_lut_samples: u32,
305
306 pub multiscattering_lut_dirs: u32,
309
310 pub multiscattering_lut_samples: u32,
313
314 pub sky_view_lut_samples: u32,
317
318 pub aerial_view_lut_samples: u32,
321
322 pub aerial_view_lut_max_distance: f32,
329
330 pub sky_max_samples: u32,
333
334 pub rendering_method: AtmosphereMode,
336}
337
338impl Default for AtmosphereSettings {
339 fn default() -> Self {
340 Self {
341 transmittance_lut_size: UVec2::new(256, 128),
342 transmittance_lut_samples: 40,
343 multiscattering_lut_size: UVec2::new(32, 32),
344 multiscattering_lut_dirs: 64,
345 multiscattering_lut_samples: 20,
346 sky_view_lut_size: UVec2::new(400, 200),
347 sky_view_lut_samples: 16,
348 aerial_view_lut_size: UVec3::new(32, 32, 32),
349 aerial_view_lut_samples: 10,
350 aerial_view_lut_max_distance: 3.2e4,
351 sky_max_samples: 16,
352 rendering_method: AtmosphereMode::LookupTexture,
353 }
354 }
355}
356
357#[derive(Clone, Component, Reflect, ShaderType)]
358#[reflect(Default)]
359pub struct GpuAtmosphereSettings {
360 pub transmittance_lut_size: UVec2,
361 pub multiscattering_lut_size: UVec2,
362 pub sky_view_lut_size: UVec2,
363 pub aerial_view_lut_size: UVec3,
364 pub transmittance_lut_samples: u32,
365 pub multiscattering_lut_dirs: u32,
366 pub multiscattering_lut_samples: u32,
367 pub sky_view_lut_samples: u32,
368 pub aerial_view_lut_samples: u32,
369 pub aerial_view_lut_max_distance: f32,
370 pub sky_max_samples: u32,
371 pub rendering_method: u32,
372}
373
374impl Default for GpuAtmosphereSettings {
375 fn default() -> Self {
376 AtmosphereSettings::default().into()
377 }
378}
379
380impl From<AtmosphereSettings> for GpuAtmosphereSettings {
381 fn from(s: AtmosphereSettings) -> Self {
382 Self {
383 transmittance_lut_size: s.transmittance_lut_size,
384 multiscattering_lut_size: s.multiscattering_lut_size,
385 sky_view_lut_size: s.sky_view_lut_size,
386 aerial_view_lut_size: s.aerial_view_lut_size,
387 transmittance_lut_samples: s.transmittance_lut_samples,
388 multiscattering_lut_dirs: s.multiscattering_lut_dirs,
389 multiscattering_lut_samples: s.multiscattering_lut_samples,
390 sky_view_lut_samples: s.sky_view_lut_samples,
391 aerial_view_lut_samples: s.aerial_view_lut_samples,
392 aerial_view_lut_max_distance: s.aerial_view_lut_max_distance,
393 sky_max_samples: s.sky_max_samples,
394 rendering_method: s.rendering_method as u32,
395 }
396 }
397}
398
399impl SyncComponent for AtmosphereSettings {
400 type Target = GpuAtmosphereSettings;
401}
402
403fn configure_camera_depth_usages(
404 mut cameras: Query<&mut Camera3d, (Changed<Camera3d>, With<ExtractedAtmosphere>)>,
405) {
406 for mut camera in &mut cameras {
407 camera.depth_texture_usages.0 |= TextureUsages::TEXTURE_BINDING.bits();
408 }
409}
410
411#[repr(u32)]
414#[derive(Clone, Default, Reflect, Copy)]
415pub enum AtmosphereMode {
416 #[default]
422 LookupTexture = 0,
423 Raymarched = 1,
429}