1mod environment;
37mod node;
38pub mod resources;
39
40use bevy_app::{App, Plugin, Update};
41use bevy_asset::{embedded_asset, AssetId, Assets, Handle};
42use bevy_camera::Camera3d;
43use bevy_core_pipeline::core_3d::graph::Node3d;
44use bevy_ecs::{
45 component::Component,
46 query::{Changed, QueryItem, With},
47 resource::Resource,
48 schedule::IntoScheduleConfigs,
49 system::{lifetimeless::Read, Query},
50};
51use bevy_math::{UVec2, UVec3, Vec3};
52use bevy_reflect::{std_traits::ReflectDefault, Reflect};
53use bevy_render::{
54 extract_component::UniformComponentPlugin,
55 render_resource::{DownlevelFlags, ShaderType, SpecializedRenderPipelines},
56 view::Hdr,
57 RenderStartup,
58};
59use bevy_render::{
60 extract_component::{ExtractComponent, ExtractComponentPlugin},
61 render_graph::{RenderGraphExt, ViewNodeRunner},
62 render_resource::{TextureFormat, TextureUsages},
63 renderer::RenderAdapter,
64 Render, RenderApp, RenderSystems,
65};
66
67use bevy_core_pipeline::core_3d::graph::Core3d;
68use bevy_shader::load_shader_library;
69use environment::{
70 init_atmosphere_probe_layout, init_atmosphere_probe_pipeline,
71 prepare_atmosphere_probe_bind_groups, prepare_atmosphere_probe_components,
72 prepare_probe_textures, AtmosphereEnvironmentMap, EnvironmentNode,
73};
74use resources::{
75 prepare_atmosphere_transforms, prepare_atmosphere_uniforms, queue_render_sky_pipelines,
76 AtmosphereTransforms, GpuAtmosphere, RenderSkyBindGroupLayouts,
77};
78use tracing::warn;
79
80use crate::{
81 medium::ScatteringMedium,
82 resources::{init_atmosphere_buffer, write_atmosphere_buffer},
83};
84
85use self::{
86 node::{AtmosphereLutsNode, AtmosphereNode, RenderSkyNode},
87 resources::{
88 prepare_atmosphere_bind_groups, prepare_atmosphere_textures, AtmosphereBindGroupLayouts,
89 AtmosphereLutPipelines, AtmosphereSampler,
90 },
91};
92
93#[doc(hidden)]
94pub struct AtmospherePlugin;
95
96impl Plugin for AtmospherePlugin {
97 fn build(&self, app: &mut App) {
98 load_shader_library!(app, "types.wgsl");
99 load_shader_library!(app, "functions.wgsl");
100 load_shader_library!(app, "bruneton_functions.wgsl");
101 load_shader_library!(app, "bindings.wgsl");
102
103 embedded_asset!(app, "transmittance_lut.wgsl");
104 embedded_asset!(app, "multiscattering_lut.wgsl");
105 embedded_asset!(app, "sky_view_lut.wgsl");
106 embedded_asset!(app, "aerial_view_lut.wgsl");
107 embedded_asset!(app, "render_sky.wgsl");
108 embedded_asset!(app, "environment.wgsl");
109
110 app.add_plugins((
111 ExtractComponentPlugin::<Atmosphere>::default(),
112 ExtractComponentPlugin::<GpuAtmosphereSettings>::default(),
113 ExtractComponentPlugin::<AtmosphereEnvironmentMap>::default(),
114 UniformComponentPlugin::<GpuAtmosphere>::default(),
115 UniformComponentPlugin::<GpuAtmosphereSettings>::default(),
116 ))
117 .add_systems(Update, prepare_atmosphere_probe_components);
118
119 let world = app.world_mut();
120 let earthlike_medium = world
121 .resource_mut::<Assets<ScatteringMedium>>()
122 .add(ScatteringMedium::earthlike(256, 256));
123 world.insert_resource(EarthlikeAtmosphere(Atmosphere::earthlike(earthlike_medium)));
124 }
125
126 fn finish(&self, app: &mut App) {
127 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
128 return;
129 };
130
131 let render_adapter = render_app.world().resource::<RenderAdapter>();
132
133 if !render_adapter
134 .get_downlevel_capabilities()
135 .flags
136 .contains(DownlevelFlags::COMPUTE_SHADERS)
137 {
138 warn!("AtmospherePlugin not loaded. GPU lacks support for compute shaders.");
139 return;
140 }
141
142 if !render_adapter
143 .get_texture_format_features(TextureFormat::Rgba16Float)
144 .allowed_usages
145 .contains(TextureUsages::STORAGE_BINDING)
146 {
147 warn!("AtmospherePlugin not loaded. GPU lacks support: TextureFormat::Rgba16Float does not support TextureUsages::STORAGE_BINDING.");
148 return;
149 }
150
151 render_app
152 .insert_resource(AtmosphereBindGroupLayouts::new())
153 .init_resource::<RenderSkyBindGroupLayouts>()
154 .init_resource::<AtmosphereSampler>()
155 .init_resource::<AtmosphereLutPipelines>()
156 .init_resource::<AtmosphereTransforms>()
157 .init_resource::<SpecializedRenderPipelines<RenderSkyBindGroupLayouts>>()
158 .add_systems(
159 RenderStartup,
160 (
161 init_atmosphere_probe_layout,
162 init_atmosphere_probe_pipeline,
163 init_atmosphere_buffer,
164 )
165 .chain(),
166 )
167 .add_systems(
168 Render,
169 (
170 configure_camera_depth_usages.in_set(RenderSystems::ManageViews),
171 queue_render_sky_pipelines.in_set(RenderSystems::Queue),
172 prepare_atmosphere_textures.in_set(RenderSystems::PrepareResources),
173 prepare_probe_textures
174 .in_set(RenderSystems::PrepareResources)
175 .after(prepare_atmosphere_textures),
176 prepare_atmosphere_uniforms
177 .before(RenderSystems::PrepareResources)
178 .after(RenderSystems::PrepareAssets),
179 prepare_atmosphere_probe_bind_groups.in_set(RenderSystems::PrepareBindGroups),
180 prepare_atmosphere_transforms.in_set(RenderSystems::PrepareResources),
181 prepare_atmosphere_bind_groups.in_set(RenderSystems::PrepareBindGroups),
182 write_atmosphere_buffer.in_set(RenderSystems::PrepareResources),
183 ),
184 )
185 .add_render_graph_node::<ViewNodeRunner<AtmosphereLutsNode>>(
186 Core3d,
187 AtmosphereNode::RenderLuts,
188 )
189 .add_render_graph_edges(
190 Core3d,
191 (
192 Node3d::EndPrepasses,
194 AtmosphereNode::RenderLuts,
195 Node3d::StartMainPass,
196 ),
197 )
198 .add_render_graph_node::<ViewNodeRunner<RenderSkyNode>>(
199 Core3d,
200 AtmosphereNode::RenderSky,
201 )
202 .add_render_graph_node::<EnvironmentNode>(Core3d, AtmosphereNode::Environment)
203 .add_render_graph_edges(
204 Core3d,
205 (
206 Node3d::MainOpaquePass,
207 AtmosphereNode::RenderSky,
208 Node3d::MainTransparentPass,
209 ),
210 );
211 }
212}
213
214#[derive(Resource)]
215pub struct EarthlikeAtmosphere(Atmosphere);
216
217impl EarthlikeAtmosphere {
218 pub fn get(&self) -> Atmosphere {
219 self.0.clone()
220 }
221}
222
223#[derive(Clone, Component)]
225#[require(AtmosphereSettings, Hdr)]
226pub struct Atmosphere {
227 pub bottom_radius: f32,
231
232 pub top_radius: f32,
237
238 pub ground_albedo: Vec3,
243
244 pub medium: Handle<ScatteringMedium>,
247}
248
249impl Atmosphere {
250 pub fn earthlike(medium: Handle<ScatteringMedium>) -> Self {
251 const EARTH_BOTTOM_RADIUS: f32 = 6_360_000.0;
252 const EARTH_TOP_RADIUS: f32 = 6_460_000.0;
253 const EARTH_ALBEDO: Vec3 = Vec3::splat(0.3);
254 Self {
255 bottom_radius: EARTH_BOTTOM_RADIUS,
256 top_radius: EARTH_TOP_RADIUS,
257 ground_albedo: EARTH_ALBEDO,
258 medium,
259 }
260 }
261}
262
263impl ExtractComponent for Atmosphere {
264 type QueryData = Read<Atmosphere>;
265
266 type QueryFilter = With<Camera3d>;
267
268 type Out = ExtractedAtmosphere;
269
270 fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
271 Some(ExtractedAtmosphere {
272 bottom_radius: item.bottom_radius,
273 top_radius: item.top_radius,
274 ground_albedo: item.ground_albedo,
275 medium: item.medium.id(),
276 })
277 }
278}
279
280#[derive(Clone, Component)]
283pub struct ExtractedAtmosphere {
284 pub bottom_radius: f32,
285 pub top_radius: f32,
286 pub ground_albedo: Vec3,
287 pub medium: AssetId<ScatteringMedium>,
288}
289
290#[derive(Clone, Component, Reflect)]
309#[reflect(Clone, Default)]
310pub struct AtmosphereSettings {
311 pub transmittance_lut_size: UVec2,
313
314 pub multiscattering_lut_size: UVec2,
316
317 pub sky_view_lut_size: UVec2,
319
320 pub aerial_view_lut_size: UVec3,
322
323 pub transmittance_lut_samples: u32,
326
327 pub multiscattering_lut_dirs: u32,
330
331 pub multiscattering_lut_samples: u32,
334
335 pub sky_view_lut_samples: u32,
338
339 pub aerial_view_lut_samples: u32,
342
343 pub aerial_view_lut_max_distance: f32,
350
351 pub scene_units_to_m: f32,
354
355 pub sky_max_samples: u32,
358
359 pub rendering_method: AtmosphereMode,
361}
362
363impl Default for AtmosphereSettings {
364 fn default() -> Self {
365 Self {
366 transmittance_lut_size: UVec2::new(256, 128),
367 transmittance_lut_samples: 40,
368 multiscattering_lut_size: UVec2::new(32, 32),
369 multiscattering_lut_dirs: 64,
370 multiscattering_lut_samples: 20,
371 sky_view_lut_size: UVec2::new(400, 200),
372 sky_view_lut_samples: 16,
373 aerial_view_lut_size: UVec3::new(32, 32, 32),
374 aerial_view_lut_samples: 10,
375 aerial_view_lut_max_distance: 3.2e4,
376 scene_units_to_m: 1.0,
377 sky_max_samples: 16,
378 rendering_method: AtmosphereMode::LookupTexture,
379 }
380 }
381}
382
383#[derive(Clone, Component, Reflect, ShaderType)]
384#[reflect(Default)]
385pub struct GpuAtmosphereSettings {
386 pub transmittance_lut_size: UVec2,
387 pub multiscattering_lut_size: UVec2,
388 pub sky_view_lut_size: UVec2,
389 pub aerial_view_lut_size: UVec3,
390 pub transmittance_lut_samples: u32,
391 pub multiscattering_lut_dirs: u32,
392 pub multiscattering_lut_samples: u32,
393 pub sky_view_lut_samples: u32,
394 pub aerial_view_lut_samples: u32,
395 pub aerial_view_lut_max_distance: f32,
396 pub scene_units_to_m: f32,
397 pub sky_max_samples: u32,
398 pub rendering_method: u32,
399}
400
401impl Default for GpuAtmosphereSettings {
402 fn default() -> Self {
403 AtmosphereSettings::default().into()
404 }
405}
406
407impl From<AtmosphereSettings> for GpuAtmosphereSettings {
408 fn from(s: AtmosphereSettings) -> Self {
409 Self {
410 transmittance_lut_size: s.transmittance_lut_size,
411 multiscattering_lut_size: s.multiscattering_lut_size,
412 sky_view_lut_size: s.sky_view_lut_size,
413 aerial_view_lut_size: s.aerial_view_lut_size,
414 transmittance_lut_samples: s.transmittance_lut_samples,
415 multiscattering_lut_dirs: s.multiscattering_lut_dirs,
416 multiscattering_lut_samples: s.multiscattering_lut_samples,
417 sky_view_lut_samples: s.sky_view_lut_samples,
418 aerial_view_lut_samples: s.aerial_view_lut_samples,
419 aerial_view_lut_max_distance: s.aerial_view_lut_max_distance,
420 scene_units_to_m: s.scene_units_to_m,
421 sky_max_samples: s.sky_max_samples,
422 rendering_method: s.rendering_method as u32,
423 }
424 }
425}
426
427impl ExtractComponent for GpuAtmosphereSettings {
428 type QueryData = Read<AtmosphereSettings>;
429
430 type QueryFilter = (With<Camera3d>, With<Atmosphere>);
431
432 type Out = GpuAtmosphereSettings;
433
434 fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
435 Some(item.clone().into())
436 }
437}
438
439fn configure_camera_depth_usages(
440 mut cameras: Query<&mut Camera3d, (Changed<Camera3d>, With<ExtractedAtmosphere>)>,
441) {
442 for mut camera in &mut cameras {
443 camera.depth_texture_usages.0 |= TextureUsages::TEXTURE_BINDING.bits();
444 }
445}
446
447#[repr(u32)]
450#[derive(Clone, Default, Reflect, Copy)]
451pub enum AtmosphereMode {
452 #[default]
458 LookupTexture = 0,
459 Raymarched = 1,
465}