1use bevy_app::{App, Plugin};
2use bevy_color::{ColorToComponents, LinearRgba};
3use bevy_ecs::prelude::*;
4use bevy_math::{Vec3, Vec4};
5use bevy_render::{
6 extract_component::ExtractComponentPlugin,
7 render_resource::{DynamicUniformBuffer, ShaderType},
8 renderer::{RenderDevice, RenderQueue},
9 view::ExtractedView,
10 GpuResourceAppExt, Render, RenderApp, RenderSystems,
11};
12use bevy_shader::load_shader_library;
13
14use crate::{DistanceFog, FogFalloff};
15
16#[derive(Copy, Clone, ShaderType, Default, Debug)]
18pub struct GpuFog {
19 base_color: Vec4,
21 directional_light_color: Vec4,
23 be: Vec3,
26 directional_light_exponent: f32,
28 bi: Vec3,
31 mode: u32,
33}
34
35#[expect(unused, reason = "Kept in sync with `mesh_view_types.wgsl`")]
37const GPU_FOG_MODE_OFF: u32 = 0;
38const GPU_FOG_MODE_LINEAR: u32 = 1;
39const GPU_FOG_MODE_EXPONENTIAL: u32 = 2;
40const GPU_FOG_MODE_EXPONENTIAL_SQUARED: u32 = 3;
41const GPU_FOG_MODE_ATMOSPHERIC: u32 = 4;
42
43#[derive(Default, Resource)]
45pub struct FogMeta {
46 pub gpu_fogs: DynamicUniformBuffer<GpuFog>,
47}
48
49pub fn prepare_fog(
51 mut commands: Commands,
52 render_device: Res<RenderDevice>,
53 render_queue: Res<RenderQueue>,
54 mut fog_meta: ResMut<FogMeta>,
55 views: Query<(Entity, &DistanceFog), With<ExtractedView>>,
56) {
57 let views_iter = views.iter();
58 let view_count = views_iter.len();
59 let Some(mut writer) = fog_meta
60 .gpu_fogs
61 .get_writer(view_count, &render_device, &render_queue)
62 else {
63 return;
64 };
65 for (entity, fog) in views_iter {
66 let gpu_fog = {
67 match &fog.falloff {
68 FogFalloff::Linear { start, end } => GpuFog {
69 mode: GPU_FOG_MODE_LINEAR,
70 base_color: LinearRgba::from(fog.color).to_vec4(),
71 directional_light_color: LinearRgba::from(fog.directional_light_color)
72 .to_vec4(),
73 directional_light_exponent: fog.directional_light_exponent,
74 be: Vec3::new(*start, *end, 0.0),
75 ..Default::default()
76 },
77 FogFalloff::Exponential { density } => GpuFog {
78 mode: GPU_FOG_MODE_EXPONENTIAL,
79 base_color: LinearRgba::from(fog.color).to_vec4(),
80 directional_light_color: LinearRgba::from(fog.directional_light_color)
81 .to_vec4(),
82 directional_light_exponent: fog.directional_light_exponent,
83 be: Vec3::new(*density, 0.0, 0.0),
84 ..Default::default()
85 },
86 FogFalloff::ExponentialSquared { density } => GpuFog {
87 mode: GPU_FOG_MODE_EXPONENTIAL_SQUARED,
88 base_color: LinearRgba::from(fog.color).to_vec4(),
89 directional_light_color: LinearRgba::from(fog.directional_light_color)
90 .to_vec4(),
91 directional_light_exponent: fog.directional_light_exponent,
92 be: Vec3::new(*density, 0.0, 0.0),
93 ..Default::default()
94 },
95 FogFalloff::Atmospheric {
96 extinction,
97 inscattering,
98 } => GpuFog {
99 mode: GPU_FOG_MODE_ATMOSPHERIC,
100 base_color: LinearRgba::from(fog.color).to_vec4(),
101 directional_light_color: LinearRgba::from(fog.directional_light_color)
102 .to_vec4(),
103 directional_light_exponent: fog.directional_light_exponent,
104 be: *extinction,
105 bi: *inscattering,
106 },
107 }
108 };
109
110 commands.entity(entity).insert(ViewFogUniformOffset {
112 offset: writer.write(&gpu_fog),
113 });
114 }
115}
116
117#[derive(Component)]
120pub struct ViewFogUniformOffset {
121 pub offset: u32,
122}
123
124pub struct FogPlugin;
126
127impl Plugin for FogPlugin {
128 fn build(&self, app: &mut App) {
129 load_shader_library!(app, "fog.wgsl");
130
131 app.add_plugins(ExtractComponentPlugin::<DistanceFog>::default());
132
133 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
134 render_app
135 .init_gpu_resource::<FogMeta>()
136 .add_systems(Render, prepare_fog.in_set(RenderSystems::PrepareResources));
137 }
138 }
139}