Skip to main content

wde_pbr/deferred/lights/
lights_binding.rs

1use wde_logger::prelude::*;
2
3use bevy::{ecs::system::SystemParamItem, prelude::*};
4use wde_renderer::prelude::*;
5
6use crate::deferred::lights::*;
7#[cfg(feature = "atmosphere")]
8use crate::prelude::{AtmosphereParamsUniform, AtmosphereSettings};
9use crate::prelude::{PbrParamsUniform, PbrSettings};
10
11/// Maximum number of lights in the scene.
12pub const MAX_LIGHTS: usize = 64;
13
14pub(crate) struct LightsBindingPlugin;
15impl Plugin for LightsBindingPlugin {
16    fn build(&self, app: &mut App) {
17        // Add the render bindings
18        #[cfg(feature = "atmosphere")]
19        app.init_resource::<AtmosphereSettings>()
20            .register_type::<AtmosphereSettings>();
21
22        app.init_resource::<PbrSettings>()
23            .register_type::<PbrSettings>()
24            .add_plugins(RenderDataRegisterPlugin::<LightsData>::default());
25
26        // Add the systems to extract the lights from the world, and to update the lights buffer
27        app.get_sub_app_mut(RenderApp)
28            .unwrap()
29            .init_resource::<ExtractedLights>();
30
31        #[cfg(feature = "atmosphere")]
32        app.get_sub_app_mut(RenderApp)
33            .unwrap()
34            .init_resource::<AtmosphereParamsUniform>()
35            .add_systems(Extract, extract_atmosphere_params);
36
37        app.get_sub_app_mut(RenderApp)
38            .unwrap()
39            .init_resource::<PbrParamsUniform>()
40            .add_systems(Extract, extract)
41            .add_systems(Extract, extract_pbr_params)
42            .add_systems(Render, update_lights_buffer.in_set(RenderSet::Prepare));
43    }
44}
45
46#[derive(Asset, Clone, TypePath, Default)]
47pub struct LightsData;
48impl LightsData {
49    pub const LIGHTS_BUFFER_IDX: u32 = 0;
50    pub const LIGHTS_BUFFER_STAGING_IDX: u32 = 1;
51    pub const ATMOSPHERE_PARAMS_BUFFER_IDX: u32 = 2;
52    pub const PBR_PARAMS_BUFFER_IDX: u32 = 3;
53}
54impl RenderData for LightsData {
55    type Params = ();
56
57    fn describe(_params: &mut SystemParamItem<Self::Params>, builder: &mut RenderDataBuilder) {
58        builder
59            .add_buffer(
60                Self::LIGHTS_BUFFER_IDX,
61                Buffer {
62                    label: "lights-buffer-gpu".to_string(),
63                    size: std::mem::size_of::<LightsStorageElement>() * MAX_LIGHTS,
64                    usage: BufferUsage::STORAGE | BufferUsage::COPY_DST,
65                    content: None
66                }
67            )
68            .add_buffer(
69                Self::LIGHTS_BUFFER_STAGING_IDX,
70                Buffer {
71                    label: "lights-buffer-staging".to_string(),
72                    size: std::mem::size_of::<LightsStorageElement>() * MAX_LIGHTS,
73                    usage: BufferUsage::COPY_SRC | BufferUsage::COPY_DST,
74                    content: None
75                }
76            );
77        #[cfg(feature = "atmosphere")]
78        builder.add_buffer(
79            Self::ATMOSPHERE_PARAMS_BUFFER_IDX,
80            Buffer {
81                label: "atmosphere-params".to_string(),
82                size: std::mem::size_of::<AtmosphereParamsUniform>(),
83                usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST,
84                content: Some(bytemuck::cast_slice(&[AtmosphereParamsUniform::default()]).to_vec())
85            }
86        );
87        builder.add_buffer(
88            Self::PBR_PARAMS_BUFFER_IDX,
89            Buffer {
90                label: "pbr-params".to_string(),
91                size: std::mem::size_of::<PbrParamsUniform>(),
92                usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST,
93                content: Some(bytemuck::cast_slice(&[PbrParamsUniform::default()]).to_vec())
94            }
95        );
96    }
97}
98
99#[derive(Resource, Default)]
100struct ExtractedLights {
101    pub directional_lights: Vec<DirectionalLight>,
102    pub point_lights: Vec<PointLight>,
103    pub spot_lights: Vec<SpotLight>
104}
105
106fn extract(
107    lights_directional: ExtractWorld<Query<&DirectionalLight>>,
108    lights_point: ExtractWorld<Query<&PointLight>>,
109    lights_spot: ExtractWorld<Query<&SpotLight>>,
110    mut extracted_lights: ResMut<ExtractedLights>
111) {
112    // Extract lights each frame. This is necessary to keep the lights buffer up to date, and to handle dynamic lights.
113    extracted_lights.directional_lights = lights_directional.iter().copied().collect();
114    extracted_lights.point_lights = lights_point.iter().copied().collect();
115    extracted_lights.spot_lights = lights_spot.iter().copied().collect();
116}
117
118#[cfg(feature = "atmosphere")]
119fn extract_atmosphere_params(
120    settings: ExtractWorld<Res<AtmosphereSettings>>,
121    time: ExtractWorld<Res<Time>>,
122    mut uniform: ResMut<AtmosphereParamsUniform>
123) {
124    let day_length = settings.day_length_seconds.max(1.0);
125    let time_of_day = if settings.animate_time {
126        (time.elapsed_secs() / day_length).fract()
127    } else {
128        settings.time_of_day.fract()
129    };
130    *uniform = AtmosphereParamsUniform::from_settings(&settings, time_of_day);
131}
132
133fn extract_pbr_params(
134    settings: ExtractWorld<Res<PbrSettings>>,
135    mut uniform: ResMut<PbrParamsUniform>
136) {
137    *uniform = PbrParamsUniform::from_settings(&settings);
138}
139
140fn update_lights_buffer(
141    lights_buffer: ResRenderData<LightsData>,
142    buffers: Res<RenderAssets<GpuBuffer>>,
143    render_instance: Res<RenderInstance>,
144    extracted_lights: Res<ExtractedLights>,
145    #[cfg(feature = "atmosphere")] atmosphere_uniform: Res<AtmosphereParamsUniform>,
146    pbr_uniform: Res<PbrParamsUniform>
147) {
148    // Get the lights buffer
149    let lights_buffer_cpu = match lights_buffer.iter().next() {
150        Some((_, buffer)) => match buffers.get(
151            &buffer
152                .get_buffer(LightsData::LIGHTS_BUFFER_STAGING_IDX)
153                .unwrap()
154        ) {
155            Some(lights_buffer) => lights_buffer,
156            None => return
157        },
158        None => return
159    };
160
161    let render_instance = render_instance.0.read().unwrap();
162    let mut offset = 0;
163    for light in extracted_lights.directional_lights.iter() {
164        let data = LightsStorageElement::from_directional(light);
165        lights_buffer_cpu.buffer.write(
166            &render_instance,
167            bytemuck::bytes_of(&data),
168            offset * std::mem::size_of::<LightsStorageElement>()
169        );
170        offset += 1;
171    }
172    for light in extracted_lights.point_lights.iter() {
173        let data = LightsStorageElement::from_point(light);
174        lights_buffer_cpu.buffer.write(
175            &render_instance,
176            bytemuck::bytes_of(&data),
177            offset * std::mem::size_of::<LightsStorageElement>()
178        );
179        offset += 1;
180    }
181    for light in extracted_lights.spot_lights.iter() {
182        let data = LightsStorageElement::from_spot(light);
183        lights_buffer_cpu.buffer.write(
184            &render_instance,
185            bytemuck::bytes_of(&data),
186            offset * std::mem::size_of::<LightsStorageElement>()
187        );
188        offset += 1;
189    }
190    if offset > MAX_LIGHTS {
191        warn!(
192            "Number of lights exceeded the maximum of {}. Some lights will be ignored in rendering.",
193            MAX_LIGHTS
194        );
195    }
196
197    // Update the buffer
198    let lights_buffer_gpu = match buffers.get(
199        &lights_buffer
200            .iter()
201            .next()
202            .unwrap()
203            .1
204            .get_buffer(LightsData::LIGHTS_BUFFER_IDX)
205            .unwrap()
206    ) {
207        Some(buffer) => buffer,
208        None => return
209    };
210    lights_buffer_gpu
211        .buffer
212        .copy_from_buffer(&render_instance, &lights_buffer_cpu.buffer);
213
214    // Update atmosphere uniform buffer stored in the same render data as lights.
215    #[cfg(feature = "atmosphere")]
216    {
217        let atmosphere_buffer = match buffers.get(
218            &lights_buffer
219                .iter()
220                .next()
221                .unwrap()
222                .1
223                .get_buffer(LightsData::ATMOSPHERE_PARAMS_BUFFER_IDX)
224                .unwrap()
225        ) {
226            Some(buffer) => buffer,
227            None => return
228        };
229        atmosphere_buffer.buffer.write(
230            &render_instance,
231            bytemuck::cast_slice(&[*atmosphere_uniform]),
232            0
233        );
234    }
235
236    let pbr_buffer = match buffers.get(
237        &lights_buffer
238            .iter()
239            .next()
240            .unwrap()
241            .1
242            .get_buffer(LightsData::PBR_PARAMS_BUFFER_IDX)
243            .unwrap()
244    ) {
245        Some(buffer) => buffer,
246        None => return
247    };
248    pbr_buffer
249        .buffer
250        .write(&render_instance, bytemuck::cast_slice(&[*pbr_uniform]), 0);
251}