bevy_pbr/lightmap/
mod.rs

1//! Lightmaps, baked lighting textures that can be applied at runtime to provide
2//! diffuse global illumination.
3//!
4//! Bevy doesn't currently have any way to actually bake lightmaps, but they can
5//! be baked in an external tool like [Blender](http://blender.org), for example
6//! with an addon like [The Lightmapper]. The tools in the [`bevy-baked-gi`]
7//! project support other lightmap baking methods.
8//!
9//! When a [`Lightmap`] component is added to an entity with a [`Mesh3d`] and a
10//! [`MeshMaterial3d<StandardMaterial>`], Bevy applies the lightmap when rendering. The brightness
11//! of the lightmap may be controlled with the `lightmap_exposure` field on
12//! [`StandardMaterial`].
13//!
14//! During the rendering extraction phase, we extract all lightmaps into the
15//! [`RenderLightmaps`] table, which lives in the render world. Mesh bindgroup
16//! and mesh uniform creation consults this table to determine which lightmap to
17//! supply to the shader. Essentially, the lightmap is a special type of texture
18//! that is part of the mesh instance rather than part of the material (because
19//! multiple meshes can share the same material, whereas sharing lightmaps is
20//! nonsensical).
21//!
22//! Note that multiple meshes can't be drawn in a single drawcall if they use
23//! different lightmap textures, unless bindless textures are in use. If you
24//! want to instance a lightmapped mesh, and your platform doesn't support
25//! bindless textures, combine the lightmap textures into a single atlas, and
26//! set the `uv_rect` field on [`Lightmap`] appropriately.
27//!
28//! [The Lightmapper]: https://github.com/Naxela/The_Lightmapper
29//! [`Mesh3d`]: bevy_render::mesh::Mesh3d
30//! [`MeshMaterial3d<StandardMaterial>`]: crate::StandardMaterial
31//! [`StandardMaterial`]: crate::StandardMaterial
32//! [`bevy-baked-gi`]: https://github.com/pcwalton/bevy-baked-gi
33
34use bevy_app::{App, Plugin};
35use bevy_asset::{load_internal_asset, weak_handle, AssetId, Handle};
36use bevy_derive::{Deref, DerefMut};
37use bevy_ecs::{
38    component::Component,
39    entity::Entity,
40    query::{Changed, Or},
41    reflect::ReflectComponent,
42    removal_detection::RemovedComponents,
43    resource::Resource,
44    schedule::IntoScheduleConfigs,
45    system::{Query, Res, ResMut},
46    world::{FromWorld, World},
47};
48use bevy_image::Image;
49use bevy_math::{uvec2, vec4, Rect, UVec2};
50use bevy_platform::collections::HashSet;
51use bevy_reflect::{std_traits::ReflectDefault, Reflect};
52use bevy_render::{
53    render_asset::RenderAssets,
54    render_resource::{Sampler, Shader, TextureView, WgpuSampler, WgpuTextureView},
55    renderer::RenderAdapter,
56    sync_world::MainEntity,
57    texture::{FallbackImage, GpuImage},
58    view::ViewVisibility,
59    Extract, ExtractSchedule, RenderApp,
60};
61use bevy_render::{renderer::RenderDevice, sync_world::MainEntityHashMap};
62use bevy_utils::default;
63use fixedbitset::FixedBitSet;
64use nonmax::{NonMaxU16, NonMaxU32};
65use tracing::error;
66
67use crate::{binding_arrays_are_usable, ExtractMeshesSet};
68
69/// The ID of the lightmap shader.
70pub const LIGHTMAP_SHADER_HANDLE: Handle<Shader> =
71    weak_handle!("fc28203f-f258-47f3-973c-ce7d1dd70e59");
72
73/// The number of lightmaps that we store in a single slab, if bindless textures
74/// are in use.
75///
76/// If bindless textures aren't in use, then only a single lightmap can be bound
77/// at a time.
78pub const LIGHTMAPS_PER_SLAB: usize = 4;
79
80/// A plugin that provides an implementation of lightmaps.
81pub struct LightmapPlugin;
82
83/// A component that applies baked indirect diffuse global illumination from a
84/// lightmap.
85///
86/// When assigned to an entity that contains a [`Mesh3d`](bevy_render::mesh::Mesh3d) and a
87/// [`MeshMaterial3d<StandardMaterial>`](crate::StandardMaterial), if the mesh
88/// has a second UV layer ([`ATTRIBUTE_UV_1`](bevy_render::mesh::Mesh::ATTRIBUTE_UV_1)),
89/// then the lightmap will render using those UVs.
90#[derive(Component, Clone, Reflect)]
91#[reflect(Component, Default, Clone)]
92pub struct Lightmap {
93    /// The lightmap texture.
94    pub image: Handle<Image>,
95
96    /// The rectangle within the lightmap texture that the UVs are relative to.
97    ///
98    /// The top left coordinate is the `min` part of the rect, and the bottom
99    /// right coordinate is the `max` part of the rect. The rect ranges from (0,
100    /// 0) to (1, 1).
101    ///
102    /// This field allows lightmaps for a variety of meshes to be packed into a
103    /// single atlas.
104    pub uv_rect: Rect,
105
106    /// Whether bicubic sampling should be used for sampling this lightmap.
107    ///
108    /// Bicubic sampling is higher quality, but slower, and may lead to light leaks.
109    ///
110    /// If true, the lightmap texture's sampler must be set to [`bevy_image::ImageSampler::linear`].
111    pub bicubic_sampling: bool,
112}
113
114/// Lightmap data stored in the render world.
115///
116/// There is one of these per visible lightmapped mesh instance.
117#[derive(Debug)]
118pub(crate) struct RenderLightmap {
119    /// The rectangle within the lightmap texture that the UVs are relative to.
120    ///
121    /// The top left coordinate is the `min` part of the rect, and the bottom
122    /// right coordinate is the `max` part of the rect. The rect ranges from (0,
123    /// 0) to (1, 1).
124    pub(crate) uv_rect: Rect,
125
126    /// The index of the slab (i.e. binding array) in which the lightmap is
127    /// located.
128    pub(crate) slab_index: LightmapSlabIndex,
129
130    /// The index of the slot (i.e. element within the binding array) in which
131    /// the lightmap is located.
132    ///
133    /// If bindless lightmaps aren't in use, this will be 0.
134    pub(crate) slot_index: LightmapSlotIndex,
135
136    // Whether or not bicubic sampling should be used for this lightmap.
137    pub(crate) bicubic_sampling: bool,
138}
139
140/// Stores data for all lightmaps in the render world.
141///
142/// This is cleared and repopulated each frame during the `extract_lightmaps`
143/// system.
144#[derive(Resource)]
145pub struct RenderLightmaps {
146    /// The mapping from every lightmapped entity to its lightmap info.
147    ///
148    /// Entities without lightmaps, or for which the mesh or lightmap isn't
149    /// loaded, won't have entries in this table.
150    pub(crate) render_lightmaps: MainEntityHashMap<RenderLightmap>,
151
152    /// The slabs (binding arrays) containing the lightmaps.
153    pub(crate) slabs: Vec<LightmapSlab>,
154
155    free_slabs: FixedBitSet,
156
157    pending_lightmaps: HashSet<(LightmapSlabIndex, LightmapSlotIndex)>,
158
159    /// Whether bindless textures are supported on this platform.
160    pub(crate) bindless_supported: bool,
161}
162
163/// A binding array that contains lightmaps.
164///
165/// This will have a single binding if bindless lightmaps aren't in use.
166pub struct LightmapSlab {
167    /// The GPU images in this slab.
168    lightmaps: Vec<AllocatedLightmap>,
169    free_slots_bitmask: u32,
170}
171
172struct AllocatedLightmap {
173    gpu_image: GpuImage,
174    // This will only be present if the lightmap is allocated but not loaded.
175    asset_id: Option<AssetId<Image>>,
176}
177
178/// The index of the slab (binding array) in which a lightmap is located.
179#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)]
180#[repr(transparent)]
181pub struct LightmapSlabIndex(pub(crate) NonMaxU32);
182
183/// The index of the slot (element within the binding array) in the slab in
184/// which a lightmap is located.
185#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)]
186#[repr(transparent)]
187pub struct LightmapSlotIndex(pub(crate) NonMaxU16);
188
189impl Plugin for LightmapPlugin {
190    fn build(&self, app: &mut App) {
191        load_internal_asset!(
192            app,
193            LIGHTMAP_SHADER_HANDLE,
194            "lightmap.wgsl",
195            Shader::from_wgsl
196        );
197    }
198
199    fn finish(&self, app: &mut App) {
200        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
201            return;
202        };
203
204        render_app
205            .init_resource::<RenderLightmaps>()
206            .add_systems(ExtractSchedule, extract_lightmaps.after(ExtractMeshesSet));
207    }
208}
209
210/// Extracts all lightmaps from the scene and populates the [`RenderLightmaps`]
211/// resource.
212fn extract_lightmaps(
213    render_lightmaps: ResMut<RenderLightmaps>,
214    changed_lightmaps_query: Extract<
215        Query<
216            (Entity, &ViewVisibility, &Lightmap),
217            Or<(Changed<ViewVisibility>, Changed<Lightmap>)>,
218        >,
219    >,
220    mut removed_lightmaps_query: Extract<RemovedComponents<Lightmap>>,
221    images: Res<RenderAssets<GpuImage>>,
222    fallback_images: Res<FallbackImage>,
223) {
224    let render_lightmaps = render_lightmaps.into_inner();
225
226    // Loop over each entity.
227    for (entity, view_visibility, lightmap) in changed_lightmaps_query.iter() {
228        if render_lightmaps
229            .render_lightmaps
230            .contains_key(&MainEntity::from(entity))
231        {
232            continue;
233        }
234
235        // Only process visible entities.
236        if !view_visibility.get() {
237            continue;
238        }
239
240        let (slab_index, slot_index) =
241            render_lightmaps.allocate(&fallback_images, lightmap.image.id());
242        render_lightmaps.render_lightmaps.insert(
243            entity.into(),
244            RenderLightmap::new(
245                lightmap.uv_rect,
246                slab_index,
247                slot_index,
248                lightmap.bicubic_sampling,
249            ),
250        );
251
252        render_lightmaps
253            .pending_lightmaps
254            .insert((slab_index, slot_index));
255    }
256
257    for entity in removed_lightmaps_query.read() {
258        if changed_lightmaps_query.contains(entity) {
259            continue;
260        }
261
262        let Some(RenderLightmap {
263            slab_index,
264            slot_index,
265            ..
266        }) = render_lightmaps
267            .render_lightmaps
268            .remove(&MainEntity::from(entity))
269        else {
270            continue;
271        };
272
273        render_lightmaps.remove(&fallback_images, slab_index, slot_index);
274        render_lightmaps
275            .pending_lightmaps
276            .remove(&(slab_index, slot_index));
277    }
278
279    render_lightmaps
280        .pending_lightmaps
281        .retain(|&(slab_index, slot_index)| {
282            let Some(asset_id) = render_lightmaps.slabs[usize::from(slab_index)].lightmaps
283                [usize::from(slot_index)]
284            .asset_id
285            else {
286                error!(
287                    "Allocated lightmap should have been removed from `pending_lightmaps` by now"
288                );
289                return false;
290            };
291
292            let Some(gpu_image) = images.get(asset_id) else {
293                return true;
294            };
295            render_lightmaps.slabs[usize::from(slab_index)].insert(slot_index, gpu_image.clone());
296            false
297        });
298}
299
300impl RenderLightmap {
301    /// Creates a new lightmap from a texture, a UV rect, and a slab and slot
302    /// index pair.
303    fn new(
304        uv_rect: Rect,
305        slab_index: LightmapSlabIndex,
306        slot_index: LightmapSlotIndex,
307        bicubic_sampling: bool,
308    ) -> Self {
309        Self {
310            uv_rect,
311            slab_index,
312            slot_index,
313            bicubic_sampling,
314        }
315    }
316}
317
318/// Packs the lightmap UV rect into 64 bits (4 16-bit unsigned integers).
319pub(crate) fn pack_lightmap_uv_rect(maybe_rect: Option<Rect>) -> UVec2 {
320    match maybe_rect {
321        Some(rect) => {
322            let rect_uvec4 = (vec4(rect.min.x, rect.min.y, rect.max.x, rect.max.y) * 65535.0)
323                .round()
324                .as_uvec4();
325            uvec2(
326                rect_uvec4.x | (rect_uvec4.y << 16),
327                rect_uvec4.z | (rect_uvec4.w << 16),
328            )
329        }
330        None => UVec2::ZERO,
331    }
332}
333
334impl Default for Lightmap {
335    fn default() -> Self {
336        Self {
337            image: Default::default(),
338            uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0),
339            bicubic_sampling: false,
340        }
341    }
342}
343
344impl FromWorld for RenderLightmaps {
345    fn from_world(world: &mut World) -> Self {
346        let render_device = world.resource::<RenderDevice>();
347        let render_adapter = world.resource::<RenderAdapter>();
348
349        let bindless_supported = binding_arrays_are_usable(render_device, render_adapter);
350
351        RenderLightmaps {
352            render_lightmaps: default(),
353            slabs: vec![],
354            free_slabs: FixedBitSet::new(),
355            pending_lightmaps: default(),
356            bindless_supported,
357        }
358    }
359}
360
361impl RenderLightmaps {
362    /// Creates a new slab, appends it to the end of the list, and returns its
363    /// slab index.
364    fn create_slab(&mut self, fallback_images: &FallbackImage) -> LightmapSlabIndex {
365        let slab_index = LightmapSlabIndex::from(self.slabs.len());
366        self.free_slabs.grow_and_insert(slab_index.into());
367        self.slabs
368            .push(LightmapSlab::new(fallback_images, self.bindless_supported));
369        slab_index
370    }
371
372    fn allocate(
373        &mut self,
374        fallback_images: &FallbackImage,
375        image_id: AssetId<Image>,
376    ) -> (LightmapSlabIndex, LightmapSlotIndex) {
377        let slab_index = match self.free_slabs.minimum() {
378            None => self.create_slab(fallback_images),
379            Some(slab_index) => slab_index.into(),
380        };
381
382        let slab = &mut self.slabs[usize::from(slab_index)];
383        let slot_index = slab.allocate(image_id);
384        if slab.is_full() {
385            self.free_slabs.remove(slab_index.into());
386        }
387
388        (slab_index, slot_index)
389    }
390
391    fn remove(
392        &mut self,
393        fallback_images: &FallbackImage,
394        slab_index: LightmapSlabIndex,
395        slot_index: LightmapSlotIndex,
396    ) {
397        let slab = &mut self.slabs[usize::from(slab_index)];
398        slab.remove(fallback_images, slot_index);
399
400        if !slab.is_full() {
401            self.free_slabs.grow_and_insert(slot_index.into());
402        }
403    }
404}
405
406impl LightmapSlab {
407    fn new(fallback_images: &FallbackImage, bindless_supported: bool) -> LightmapSlab {
408        let count = if bindless_supported {
409            LIGHTMAPS_PER_SLAB
410        } else {
411            1
412        };
413
414        LightmapSlab {
415            lightmaps: (0..count)
416                .map(|_| AllocatedLightmap {
417                    gpu_image: fallback_images.d2.clone(),
418                    asset_id: None,
419                })
420                .collect(),
421            free_slots_bitmask: (1 << count) - 1,
422        }
423    }
424
425    fn is_full(&self) -> bool {
426        self.free_slots_bitmask == 0
427    }
428
429    fn allocate(&mut self, image_id: AssetId<Image>) -> LightmapSlotIndex {
430        let index = LightmapSlotIndex::from(self.free_slots_bitmask.trailing_zeros());
431        self.free_slots_bitmask &= !(1 << u32::from(index));
432        self.lightmaps[usize::from(index)].asset_id = Some(image_id);
433        index
434    }
435
436    fn insert(&mut self, index: LightmapSlotIndex, gpu_image: GpuImage) {
437        self.lightmaps[usize::from(index)] = AllocatedLightmap {
438            gpu_image,
439            asset_id: None,
440        }
441    }
442
443    fn remove(&mut self, fallback_images: &FallbackImage, index: LightmapSlotIndex) {
444        self.lightmaps[usize::from(index)] = AllocatedLightmap {
445            gpu_image: fallback_images.d2.clone(),
446            asset_id: None,
447        };
448        self.free_slots_bitmask |= 1 << u32::from(index);
449    }
450
451    /// Returns the texture views and samplers for the lightmaps in this slab,
452    /// ready to be placed into a bind group.
453    ///
454    /// This is used when constructing bind groups in bindless mode. Before
455    /// returning, this function pads out the arrays with fallback images in
456    /// order to fulfill requirements of platforms that require full binding
457    /// arrays (e.g. DX12).
458    pub(crate) fn build_binding_arrays(&self) -> (Vec<&WgpuTextureView>, Vec<&WgpuSampler>) {
459        (
460            self.lightmaps
461                .iter()
462                .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.texture_view)
463                .collect(),
464            self.lightmaps
465                .iter()
466                .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.sampler)
467                .collect(),
468        )
469    }
470
471    /// Returns the texture view and sampler corresponding to the first
472    /// lightmap, which must exist.
473    ///
474    /// This is used when constructing bind groups in non-bindless mode.
475    pub(crate) fn bindings_for_first_lightmap(&self) -> (&TextureView, &Sampler) {
476        (
477            &self.lightmaps[0].gpu_image.texture_view,
478            &self.lightmaps[0].gpu_image.sampler,
479        )
480    }
481}
482
483impl From<u32> for LightmapSlabIndex {
484    fn from(value: u32) -> Self {
485        Self(NonMaxU32::new(value).unwrap())
486    }
487}
488
489impl From<usize> for LightmapSlabIndex {
490    fn from(value: usize) -> Self {
491        Self::from(value as u32)
492    }
493}
494
495impl From<u32> for LightmapSlotIndex {
496    fn from(value: u32) -> Self {
497        Self(NonMaxU16::new(value as u16).unwrap())
498    }
499}
500
501impl From<usize> for LightmapSlotIndex {
502    fn from(value: usize) -> Self {
503        Self::from(value as u32)
504    }
505}
506
507impl From<LightmapSlabIndex> for usize {
508    fn from(value: LightmapSlabIndex) -> Self {
509        value.0.get() as usize
510    }
511}
512
513impl From<LightmapSlotIndex> for usize {
514    fn from(value: LightmapSlotIndex) -> Self {
515        value.0.get() as usize
516    }
517}
518
519impl From<LightmapSlotIndex> for u16 {
520    fn from(value: LightmapSlotIndex) -> Self {
521        value.0.get()
522    }
523}
524
525impl From<LightmapSlotIndex> for u32 {
526    fn from(value: LightmapSlotIndex) -> Self {
527        value.0.get() as u32
528    }
529}