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}