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