
# Objective A big step in the migration to required components: meshes and materials! ## Solution As per the [selected proposal](https://hackmd.io/@bevy/required_components/%2Fj9-PnF-2QKK0on1KQ29UWQ): - Deprecate `MaterialMesh2dBundle`, `MaterialMeshBundle`, and `PbrBundle`. - Add `Mesh2d` and `Mesh3d` components, which wrap a `Handle<Mesh>`. - Add `MeshMaterial2d<M: Material2d>` and `MeshMaterial3d<M: Material>`, which wrap a `Handle<M>`. - Meshes *without* a mesh material should be rendered with a default material. The existence of a material is determined by `HasMaterial2d`/`HasMaterial3d`, which is required by `MeshMaterial2d`/`MeshMaterial3d`. This gets around problems with the generics. Previously: ```rust commands.spawn(MaterialMesh2dBundle { mesh: meshes.add(Circle::new(100.0)).into(), material: materials.add(Color::srgb(7.5, 0.0, 7.5)), transform: Transform::from_translation(Vec3::new(-200., 0., 0.)), ..default() }); ``` Now: ```rust commands.spawn(( Mesh2d(meshes.add(Circle::new(100.0))), MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))), Transform::from_translation(Vec3::new(-200., 0., 0.)), )); ``` If the mesh material is missing, previously nothing was rendered. Now, it renders a white default `ColorMaterial` in 2D and a `StandardMaterial` in 3D (this can be overridden). Below, only every other entity has a material:   Why white? This is still open for discussion, but I think white makes sense for a *default* material, while *invalid* asset handles pointing to nothing should have something like a pink material to indicate that something is broken (I don't handle that in this PR yet). This is kind of a mix of Godot and Unity: Godot just renders a white material for non-existent materials, while Unity renders nothing when no materials exist, but renders pink for invalid materials. I can also change the default material to pink if that is preferable though. ## Testing I ran some 2D and 3D examples to test if anything changed visually. I have not tested all examples or features yet however. If anyone wants to test more extensively, it would be appreciated! ## Implementation Notes - The relationship between `bevy_render` and `bevy_pbr` is weird here. `bevy_render` needs `Mesh3d` for its own systems, but `bevy_pbr` has all of the material logic, and `bevy_render` doesn't depend on it. I feel like the two crates should be refactored in some way, but I think that's out of scope for this PR. - I didn't migrate meshlets to required components yet. That can probably be done in a follow-up, as this is already a huge PR. - It is becoming increasingly clear to me that we really, *really* want to disallow raw asset handles as components. They caused me a *ton* of headache here already, and it took me a long time to find every place that queried for them or inserted them directly on entities, since there were no compiler errors for it. If we don't remove the `Component` derive, I expect raw asset handles to be a *huge* footgun for users as we transition to wrapper components, especially as handles as components have been the norm so far. I personally consider this to be a blocker for 0.15: we need to migrate to wrapper components for asset handles everywhere, and remove the `Component` derive. Also see https://github.com/bevyengine/bevy/issues/14124. --- ## Migration Guide Asset handles for meshes and mesh materials must now be wrapped in the `Mesh2d` and `MeshMaterial2d` or `Mesh3d` and `MeshMaterial3d` components for 2D and 3D respectively. Raw handles as components no longer render meshes. Additionally, `MaterialMesh2dBundle`, `MaterialMeshBundle`, and `PbrBundle` have been deprecated. Instead, use the mesh and material components directly. Previously: ```rust commands.spawn(MaterialMesh2dBundle { mesh: meshes.add(Circle::new(100.0)).into(), material: materials.add(Color::srgb(7.5, 0.0, 7.5)), transform: Transform::from_translation(Vec3::new(-200., 0., 0.)), ..default() }); ``` Now: ```rust commands.spawn(( Mesh2d(meshes.add(Circle::new(100.0))), MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))), Transform::from_translation(Vec3::new(-200., 0., 0.)), )); ``` If the mesh material is missing, a white default material is now used. Previously, nothing was rendered if the material was missing. The `WithMesh2d` and `WithMesh3d` query filter type aliases have also been removed. Simply use `With<Mesh2d>` or `With<Mesh3d>`. --------- Co-authored-by: Tim Blackbird <justthecooldude@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
216 lines
7.7 KiB
Rust
216 lines
7.7 KiB
Rust
//! Lightmaps, baked lighting textures that can be applied at runtime to provide
|
|
//! diffuse global illumination.
|
|
//!
|
|
//! Bevy doesn't currently have any way to actually bake lightmaps, but they can
|
|
//! be baked in an external tool like [Blender](http://blender.org), for example
|
|
//! with an addon like [The Lightmapper]. The tools in the [`bevy-baked-gi`]
|
|
//! project support other lightmap baking methods.
|
|
//!
|
|
//! When a [`Lightmap`] component is added to an entity with a [`Mesh3d`] and a
|
|
//! [`MeshMaterial3d<StandardMaterial>`], Bevy applies the lightmap when rendering. The brightness
|
|
//! of the lightmap may be controlled with the `lightmap_exposure` field on
|
|
//! [`StandardMaterial`].
|
|
//!
|
|
//! During the rendering extraction phase, we extract all lightmaps into the
|
|
//! [`RenderLightmaps`] table, which lives in the render world. Mesh bindgroup
|
|
//! and mesh uniform creation consults this table to determine which lightmap to
|
|
//! supply to the shader. Essentially, the lightmap is a special type of texture
|
|
//! that is part of the mesh instance rather than part of the material (because
|
|
//! multiple meshes can share the same material, whereas sharing lightmaps is
|
|
//! nonsensical).
|
|
//!
|
|
//! Note that meshes can't be instanced if they use different lightmap textures.
|
|
//! If you want to instance a lightmapped mesh, combine the lightmap textures
|
|
//! into a single atlas, and set the `uv_rect` field on [`Lightmap`]
|
|
//! appropriately.
|
|
//!
|
|
//! [The Lightmapper]: https://github.com/Naxela/The_Lightmapper
|
|
//! [`Mesh3d`]: bevy_render::mesh::Mesh3d
|
|
//! [`MeshMaterial3d<StandardMaterial>`]: crate::StandardMaterial
|
|
//! [`StandardMaterial`]: crate::StandardMaterial
|
|
//! [`bevy-baked-gi`]: https://github.com/pcwalton/bevy-baked-gi
|
|
|
|
use bevy_app::{App, Plugin};
|
|
use bevy_asset::{load_internal_asset, AssetId, Handle};
|
|
use bevy_ecs::{
|
|
component::Component,
|
|
entity::{Entity, EntityHashMap},
|
|
reflect::ReflectComponent,
|
|
schedule::IntoSystemConfigs,
|
|
system::{Query, Res, ResMut, Resource},
|
|
};
|
|
use bevy_math::{uvec2, vec4, Rect, UVec2};
|
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
|
use bevy_render::{
|
|
mesh::{Mesh, RenderMesh},
|
|
render_asset::RenderAssets,
|
|
render_resource::Shader,
|
|
texture::{GpuImage, Image},
|
|
view::ViewVisibility,
|
|
Extract, ExtractSchedule, RenderApp,
|
|
};
|
|
use bevy_utils::HashSet;
|
|
|
|
use crate::{ExtractMeshesSet, RenderMeshInstances};
|
|
|
|
/// The ID of the lightmap shader.
|
|
pub const LIGHTMAP_SHADER_HANDLE: Handle<Shader> =
|
|
Handle::weak_from_u128(285484768317531991932943596447919767152);
|
|
|
|
/// A plugin that provides an implementation of lightmaps.
|
|
pub struct LightmapPlugin;
|
|
|
|
/// A component that applies baked indirect diffuse global illumination from a
|
|
/// lightmap.
|
|
///
|
|
/// When assigned to an entity that contains a [`Mesh3d`](bevy_render::mesh::Mesh3d) and a
|
|
/// [`MeshMaterial3d<StandardMaterial>`](crate::StandardMaterial), if the mesh
|
|
/// has a second UV layer ([`ATTRIBUTE_UV_1`](bevy_render::mesh::Mesh::ATTRIBUTE_UV_1)),
|
|
/// then the lightmap will render using those UVs.
|
|
#[derive(Component, Clone, Reflect)]
|
|
#[reflect(Component, Default)]
|
|
pub struct Lightmap {
|
|
/// The lightmap texture.
|
|
pub image: Handle<Image>,
|
|
|
|
/// The rectangle within the lightmap texture that the UVs are relative to.
|
|
///
|
|
/// The top left coordinate is the `min` part of the rect, and the bottom
|
|
/// right coordinate is the `max` part of the rect. The rect ranges from (0,
|
|
/// 0) to (1, 1).
|
|
///
|
|
/// This field allows lightmaps for a variety of meshes to be packed into a
|
|
/// single atlas.
|
|
pub uv_rect: Rect,
|
|
}
|
|
|
|
/// Lightmap data stored in the render world.
|
|
///
|
|
/// There is one of these per visible lightmapped mesh instance.
|
|
#[derive(Debug)]
|
|
pub(crate) struct RenderLightmap {
|
|
/// The ID of the lightmap texture.
|
|
pub(crate) image: AssetId<Image>,
|
|
|
|
/// The rectangle within the lightmap texture that the UVs are relative to.
|
|
///
|
|
/// The top left coordinate is the `min` part of the rect, and the bottom
|
|
/// right coordinate is the `max` part of the rect. The rect ranges from (0,
|
|
/// 0) to (1, 1).
|
|
pub(crate) uv_rect: Rect,
|
|
}
|
|
|
|
/// Stores data for all lightmaps in the render world.
|
|
///
|
|
/// This is cleared and repopulated each frame during the `extract_lightmaps`
|
|
/// system.
|
|
#[derive(Default, Resource)]
|
|
pub struct RenderLightmaps {
|
|
/// The mapping from every lightmapped entity to its lightmap info.
|
|
///
|
|
/// Entities without lightmaps, or for which the mesh or lightmap isn't
|
|
/// loaded, won't have entries in this table.
|
|
pub(crate) render_lightmaps: EntityHashMap<RenderLightmap>,
|
|
|
|
/// All active lightmap images in the scene.
|
|
///
|
|
/// Gathering all lightmap images into a set makes mesh bindgroup
|
|
/// preparation slightly more efficient, because only one bindgroup needs to
|
|
/// be created per lightmap texture.
|
|
pub(crate) all_lightmap_images: HashSet<AssetId<Image>>,
|
|
}
|
|
|
|
impl Plugin for LightmapPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
load_internal_asset!(
|
|
app,
|
|
LIGHTMAP_SHADER_HANDLE,
|
|
"lightmap.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
}
|
|
|
|
fn finish(&self, app: &mut App) {
|
|
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
|
return;
|
|
};
|
|
|
|
render_app
|
|
.init_resource::<RenderLightmaps>()
|
|
.add_systems(ExtractSchedule, extract_lightmaps.after(ExtractMeshesSet));
|
|
}
|
|
}
|
|
|
|
/// Extracts all lightmaps from the scene and populates the [`RenderLightmaps`]
|
|
/// resource.
|
|
fn extract_lightmaps(
|
|
mut render_lightmaps: ResMut<RenderLightmaps>,
|
|
lightmaps: Extract<Query<(Entity, &ViewVisibility, &Lightmap)>>,
|
|
render_mesh_instances: Res<RenderMeshInstances>,
|
|
images: Res<RenderAssets<GpuImage>>,
|
|
meshes: Res<RenderAssets<RenderMesh>>,
|
|
) {
|
|
// Clear out the old frame's data.
|
|
render_lightmaps.render_lightmaps.clear();
|
|
render_lightmaps.all_lightmap_images.clear();
|
|
|
|
// Loop over each entity.
|
|
for (entity, view_visibility, lightmap) in lightmaps.iter() {
|
|
// Only process visible entities for which the mesh and lightmap are
|
|
// both loaded.
|
|
if !view_visibility.get()
|
|
|| images.get(&lightmap.image).is_none()
|
|
|| !render_mesh_instances
|
|
.mesh_asset_id(entity)
|
|
.and_then(|mesh_asset_id| meshes.get(mesh_asset_id))
|
|
.is_some_and(|mesh| mesh.layout.0.contains(Mesh::ATTRIBUTE_UV_1.id))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Store information about the lightmap in the render world.
|
|
render_lightmaps.render_lightmaps.insert(
|
|
entity,
|
|
RenderLightmap::new(lightmap.image.id(), lightmap.uv_rect),
|
|
);
|
|
|
|
// Make a note of the loaded lightmap image so we can efficiently
|
|
// process them later during mesh bindgroup creation.
|
|
render_lightmaps
|
|
.all_lightmap_images
|
|
.insert(lightmap.image.id());
|
|
}
|
|
}
|
|
|
|
impl RenderLightmap {
|
|
/// Creates a new lightmap from a texture and a UV rect.
|
|
fn new(image: AssetId<Image>, uv_rect: Rect) -> Self {
|
|
Self { image, uv_rect }
|
|
}
|
|
}
|
|
|
|
/// Packs the lightmap UV rect into 64 bits (4 16-bit unsigned integers).
|
|
pub(crate) fn pack_lightmap_uv_rect(maybe_rect: Option<Rect>) -> UVec2 {
|
|
match maybe_rect {
|
|
Some(rect) => {
|
|
let rect_uvec4 = (vec4(rect.min.x, rect.min.y, rect.max.x, rect.max.y) * 65535.0)
|
|
.round()
|
|
.as_uvec4();
|
|
uvec2(
|
|
rect_uvec4.x | (rect_uvec4.y << 16),
|
|
rect_uvec4.z | (rect_uvec4.w << 16),
|
|
)
|
|
}
|
|
None => UVec2::ZERO,
|
|
}
|
|
}
|
|
|
|
impl Default for Lightmap {
|
|
fn default() -> Self {
|
|
Self {
|
|
image: Default::default(),
|
|
uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0),
|
|
}
|
|
}
|
|
}
|