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 meshes can't be instanced if they use different lightmap textures.
23//! If you want to instance a lightmapped mesh, combine the lightmap textures
24//! into a single atlas, and set the `uv_rect` field on [`Lightmap`]
25//! appropriately.
26//!
27//! [The Lightmapper]: https://github.com/Naxela/The_Lightmapper
28//! [`Mesh3d`]: bevy_render::mesh::Mesh3d
29//! [`MeshMaterial3d<StandardMaterial>`]: crate::StandardMaterial
30//! [`StandardMaterial`]: crate::StandardMaterial
31//! [`bevy-baked-gi`]: https://github.com/pcwalton/bevy-baked-gi
32
33use bevy_app::{App, Plugin};
34use bevy_asset::{load_internal_asset, AssetId, Handle};
35use bevy_ecs::{
36    component::Component,
37    entity::Entity,
38    reflect::ReflectComponent,
39    schedule::IntoSystemConfigs,
40    system::{Query, Res, ResMut, Resource},
41};
42use bevy_image::Image;
43use bevy_math::{uvec2, vec4, Rect, UVec2};
44use bevy_reflect::{std_traits::ReflectDefault, Reflect};
45use bevy_render::sync_world::MainEntityHashMap;
46use bevy_render::{
47    mesh::{Mesh, RenderMesh},
48    render_asset::RenderAssets,
49    render_resource::Shader,
50    texture::GpuImage,
51    view::ViewVisibility,
52    Extract, ExtractSchedule, RenderApp,
53};
54use bevy_utils::HashSet;
55
56use crate::{ExtractMeshesSet, RenderMeshInstances};
57
58/// The ID of the lightmap shader.
59pub const LIGHTMAP_SHADER_HANDLE: Handle<Shader> =
60    Handle::weak_from_u128(285484768317531991932943596447919767152);
61
62/// A plugin that provides an implementation of lightmaps.
63pub struct LightmapPlugin;
64
65/// A component that applies baked indirect diffuse global illumination from a
66/// lightmap.
67///
68/// When assigned to an entity that contains a [`Mesh3d`](bevy_render::mesh::Mesh3d) and a
69/// [`MeshMaterial3d<StandardMaterial>`](crate::StandardMaterial), if the mesh
70/// has a second UV layer ([`ATTRIBUTE_UV_1`](bevy_render::mesh::Mesh::ATTRIBUTE_UV_1)),
71/// then the lightmap will render using those UVs.
72#[derive(Component, Clone, Reflect)]
73#[reflect(Component, Default)]
74pub struct Lightmap {
75    /// The lightmap texture.
76    pub image: Handle<Image>,
77
78    /// The rectangle within the lightmap texture that the UVs are relative to.
79    ///
80    /// The top left coordinate is the `min` part of the rect, and the bottom
81    /// right coordinate is the `max` part of the rect. The rect ranges from (0,
82    /// 0) to (1, 1).
83    ///
84    /// This field allows lightmaps for a variety of meshes to be packed into a
85    /// single atlas.
86    pub uv_rect: Rect,
87}
88
89/// Lightmap data stored in the render world.
90///
91/// There is one of these per visible lightmapped mesh instance.
92#[derive(Debug)]
93pub(crate) struct RenderLightmap {
94    /// The ID of the lightmap texture.
95    pub(crate) image: AssetId<Image>,
96
97    /// The rectangle within the lightmap texture that the UVs are relative to.
98    ///
99    /// The top left coordinate is the `min` part of the rect, and the bottom
100    /// right coordinate is the `max` part of the rect. The rect ranges from (0,
101    /// 0) to (1, 1).
102    pub(crate) uv_rect: Rect,
103}
104
105/// Stores data for all lightmaps in the render world.
106///
107/// This is cleared and repopulated each frame during the `extract_lightmaps`
108/// system.
109#[derive(Default, Resource)]
110pub struct RenderLightmaps {
111    /// The mapping from every lightmapped entity to its lightmap info.
112    ///
113    /// Entities without lightmaps, or for which the mesh or lightmap isn't
114    /// loaded, won't have entries in this table.
115    pub(crate) render_lightmaps: MainEntityHashMap<RenderLightmap>,
116
117    /// All active lightmap images in the scene.
118    ///
119    /// Gathering all lightmap images into a set makes mesh bindgroup
120    /// preparation slightly more efficient, because only one bindgroup needs to
121    /// be created per lightmap texture.
122    pub(crate) all_lightmap_images: HashSet<AssetId<Image>>,
123}
124
125impl Plugin for LightmapPlugin {
126    fn build(&self, app: &mut App) {
127        load_internal_asset!(
128            app,
129            LIGHTMAP_SHADER_HANDLE,
130            "lightmap.wgsl",
131            Shader::from_wgsl
132        );
133    }
134
135    fn finish(&self, app: &mut App) {
136        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
137            return;
138        };
139
140        render_app
141            .init_resource::<RenderLightmaps>()
142            .add_systems(ExtractSchedule, extract_lightmaps.after(ExtractMeshesSet));
143    }
144}
145
146/// Extracts all lightmaps from the scene and populates the [`RenderLightmaps`]
147/// resource.
148fn extract_lightmaps(
149    mut render_lightmaps: ResMut<RenderLightmaps>,
150    lightmaps: Extract<Query<(Entity, &ViewVisibility, &Lightmap)>>,
151    render_mesh_instances: Res<RenderMeshInstances>,
152    images: Res<RenderAssets<GpuImage>>,
153    meshes: Res<RenderAssets<RenderMesh>>,
154) {
155    // Clear out the old frame's data.
156    render_lightmaps.render_lightmaps.clear();
157    render_lightmaps.all_lightmap_images.clear();
158
159    // Loop over each entity.
160    for (entity, view_visibility, lightmap) in lightmaps.iter() {
161        // Only process visible entities for which the mesh and lightmap are
162        // both loaded.
163        if !view_visibility.get()
164            || images.get(&lightmap.image).is_none()
165            || !render_mesh_instances
166                .mesh_asset_id(entity.into())
167                .and_then(|mesh_asset_id| meshes.get(mesh_asset_id))
168                .is_some_and(|mesh| mesh.layout.0.contains(Mesh::ATTRIBUTE_UV_1.id))
169        {
170            continue;
171        }
172
173        // Store information about the lightmap in the render world.
174        render_lightmaps.render_lightmaps.insert(
175            entity.into(),
176            RenderLightmap::new(lightmap.image.id(), lightmap.uv_rect),
177        );
178
179        // Make a note of the loaded lightmap image so we can efficiently
180        // process them later during mesh bindgroup creation.
181        render_lightmaps
182            .all_lightmap_images
183            .insert(lightmap.image.id());
184    }
185}
186
187impl RenderLightmap {
188    /// Creates a new lightmap from a texture and a UV rect.
189    fn new(image: AssetId<Image>, uv_rect: Rect) -> Self {
190        Self { image, uv_rect }
191    }
192}
193
194/// Packs the lightmap UV rect into 64 bits (4 16-bit unsigned integers).
195pub(crate) fn pack_lightmap_uv_rect(maybe_rect: Option<Rect>) -> UVec2 {
196    match maybe_rect {
197        Some(rect) => {
198            let rect_uvec4 = (vec4(rect.min.x, rect.min.y, rect.max.x, rect.max.y) * 65535.0)
199                .round()
200                .as_uvec4();
201            uvec2(
202                rect_uvec4.x | (rect_uvec4.y << 16),
203                rect_uvec4.z | (rect_uvec4.w << 16),
204            )
205        }
206        None => UVec2::ZERO,
207    }
208}
209
210impl Default for Lightmap {
211    fn default() -> Self {
212        Self {
213            image: Default::default(),
214            uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0),
215        }
216    }
217}