mirror of
https://github.com/bevyengine/bevy
synced 2024-11-13 00:17:27 +00:00
Implement lightmaps. (#10231)
![Screenshot](https://i.imgur.com/A4KzWFq.png) # Objective Lightmaps, textures that store baked global illumination, have been a mainstay of real-time graphics for decades. Bevy currently has no support for them, so this pull request implements them. ## Solution The new `Lightmap` component can be attached to any entity that contains a `Handle<Mesh>` and a `StandardMaterial`. When present, it will be applied in the PBR shader. Because multiple lightmaps are frequently packed into atlases, each lightmap may have its own UV boundaries within its texture. An `exposure` field is also provided, to control the brightness of the lightmap. Note that this PR doesn't provide any way to bake the lightmaps. That can be done with [The Lightmapper] or another solution, such as Unity's Bakery. --- ## Changelog ### Added * A new component, `Lightmap`, is available, for baked global illumination. If your mesh has a second UV channel (UV1), and you attach this component to the entity with that mesh, Bevy will apply the texture referenced in the lightmap. [The Lightmapper]: https://github.com/Naxela/The_Lightmapper --------- Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
parent
2440aa8475
commit
dd14f3a477
23 changed files with 536 additions and 45 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -871,6 +871,17 @@ description = "Showcases wireframe rendering"
|
||||||
category = "3D Rendering"
|
category = "3D Rendering"
|
||||||
wasm = false
|
wasm = false
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "lightmaps"
|
||||||
|
path = "examples/3d/lightmaps.rs"
|
||||||
|
doc-scrape-examples = true
|
||||||
|
|
||||||
|
[package.metadata.example.lightmaps]
|
||||||
|
name = "Lightmaps"
|
||||||
|
description = "Rendering a scene with baked lightmaps"
|
||||||
|
category = "3D Rendering"
|
||||||
|
wasm = false
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "no_prepass"
|
name = "no_prepass"
|
||||||
path = "tests/3d/no_prepass.rs"
|
path = "tests/3d/no_prepass.rs"
|
||||||
|
|
BIN
assets/lightmaps/CornellBox-Box.zstd.ktx2
Normal file
BIN
assets/lightmaps/CornellBox-Box.zstd.ktx2
Normal file
Binary file not shown.
BIN
assets/lightmaps/CornellBox-Large.zstd.ktx2
Normal file
BIN
assets/lightmaps/CornellBox-Large.zstd.ktx2
Normal file
Binary file not shown.
BIN
assets/lightmaps/CornellBox-Small.zstd.ktx2
Normal file
BIN
assets/lightmaps/CornellBox-Small.zstd.ktx2
Normal file
Binary file not shown.
BIN
assets/models/CornellBox/CornellBox.glb
Normal file
BIN
assets/models/CornellBox/CornellBox.glb
Normal file
Binary file not shown.
|
@ -7,6 +7,7 @@ mod environment_map;
|
||||||
mod extended_material;
|
mod extended_material;
|
||||||
mod fog;
|
mod fog;
|
||||||
mod light;
|
mod light;
|
||||||
|
mod lightmap;
|
||||||
mod material;
|
mod material;
|
||||||
mod parallax;
|
mod parallax;
|
||||||
mod pbr_material;
|
mod pbr_material;
|
||||||
|
@ -20,6 +21,7 @@ pub use environment_map::EnvironmentMapLight;
|
||||||
pub use extended_material::*;
|
pub use extended_material::*;
|
||||||
pub use fog::*;
|
pub use fog::*;
|
||||||
pub use light::*;
|
pub use light::*;
|
||||||
|
pub use lightmap::*;
|
||||||
pub use material::*;
|
pub use material::*;
|
||||||
pub use parallax::*;
|
pub use parallax::*;
|
||||||
pub use pbr_material::*;
|
pub use pbr_material::*;
|
||||||
|
@ -258,6 +260,7 @@ impl Plugin for PbrPlugin {
|
||||||
FogPlugin,
|
FogPlugin,
|
||||||
ExtractResourcePlugin::<DefaultOpaqueRendererMethod>::default(),
|
ExtractResourcePlugin::<DefaultOpaqueRendererMethod>::default(),
|
||||||
ExtractComponentPlugin::<ShadowFilteringMethod>::default(),
|
ExtractComponentPlugin::<ShadowFilteringMethod>::default(),
|
||||||
|
LightmapPlugin,
|
||||||
))
|
))
|
||||||
.configure_sets(
|
.configure_sets(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
|
|
29
crates/bevy_pbr/src/lightmap/lightmap.wgsl
Normal file
29
crates/bevy_pbr/src/lightmap/lightmap.wgsl
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#define_import_path bevy_pbr::lightmap
|
||||||
|
|
||||||
|
#import bevy_pbr::mesh_bindings::mesh
|
||||||
|
|
||||||
|
@group(1) @binding(4) var lightmaps_texture: texture_2d<f32>;
|
||||||
|
@group(1) @binding(5) var lightmaps_sampler: sampler;
|
||||||
|
|
||||||
|
// Samples the lightmap, if any, and returns indirect illumination from it.
|
||||||
|
fn lightmap(uv: vec2<f32>, exposure: f32, instance_index: u32) -> vec3<f32> {
|
||||||
|
let packed_uv_rect = mesh[instance_index].lightmap_uv_rect;
|
||||||
|
let uv_rect = vec4<f32>(vec4<u32>(
|
||||||
|
packed_uv_rect.x & 0xffffu,
|
||||||
|
packed_uv_rect.x >> 16u,
|
||||||
|
packed_uv_rect.y & 0xffffu,
|
||||||
|
packed_uv_rect.y >> 16u)) / 65535.0;
|
||||||
|
|
||||||
|
let lightmap_uv = mix(uv_rect.xy, uv_rect.zw, uv);
|
||||||
|
|
||||||
|
// Mipmapping lightmaps is usually a bad idea due to leaking across UV
|
||||||
|
// islands, so there's no harm in using mip level 0 and it lets us avoid
|
||||||
|
// control flow uniformity problems.
|
||||||
|
//
|
||||||
|
// TODO(pcwalton): Consider bicubic filtering.
|
||||||
|
return textureSampleLevel(
|
||||||
|
lightmaps_texture,
|
||||||
|
lightmaps_sampler,
|
||||||
|
lightmap_uv,
|
||||||
|
0.0).rgb * exposure;
|
||||||
|
}
|
210
crates/bevy_pbr/src/lightmap/mod.rs
Normal file
210
crates/bevy_pbr/src/lightmap/mod.rs
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
//! 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 [`Mesh`] and a
|
||||||
|
//! [`StandardMaterial`](crate::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
|
||||||
|
//!
|
||||||
|
//! [`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,
|
||||||
|
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, render_asset::RenderAssets, render_resource::Shader, texture::Image,
|
||||||
|
view::ViewVisibility, Extract, ExtractSchedule, RenderApp,
|
||||||
|
};
|
||||||
|
use bevy_utils::{EntityHashMap, HashSet};
|
||||||
|
|
||||||
|
use crate::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 [`Mesh`] and a
|
||||||
|
/// [`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<Entity, 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 Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
render_app.init_resource::<RenderLightmaps>().add_systems(
|
||||||
|
ExtractSchedule,
|
||||||
|
extract_lightmaps.after(crate::extract_meshes),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<Image>>,
|
||||||
|
meshes: Res<RenderAssets<Mesh>>,
|
||||||
|
) {
|
||||||
|
// 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
|
||||||
|
.get(&entity)
|
||||||
|
.and_then(|mesh_instance| meshes.get(mesh_instance.mesh_asset_id))
|
||||||
|
.is_some_and(|mesh| mesh.layout.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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -465,6 +465,7 @@ pub fn queue_material_meshes<M: Material>(
|
||||||
mut render_mesh_instances: ResMut<RenderMeshInstances>,
|
mut render_mesh_instances: ResMut<RenderMeshInstances>,
|
||||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||||
images: Res<RenderAssets<Image>>,
|
images: Res<RenderAssets<Image>>,
|
||||||
|
render_lightmaps: Res<RenderLightmaps>,
|
||||||
mut views: Query<(
|
mut views: Query<(
|
||||||
&ExtractedView,
|
&ExtractedView,
|
||||||
&VisibleEntities,
|
&VisibleEntities,
|
||||||
|
@ -613,6 +614,13 @@ pub fn queue_material_meshes<M: Material>(
|
||||||
|
|
||||||
mesh_key |= alpha_mode_pipeline_key(material.properties.alpha_mode);
|
mesh_key |= alpha_mode_pipeline_key(material.properties.alpha_mode);
|
||||||
|
|
||||||
|
if render_lightmaps
|
||||||
|
.render_lightmaps
|
||||||
|
.contains_key(visible_entity)
|
||||||
|
{
|
||||||
|
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
|
||||||
|
}
|
||||||
|
|
||||||
let pipeline_id = pipelines.specialize(
|
let pipeline_id = pipelines.specialize(
|
||||||
&pipeline_cache,
|
&pipeline_cache,
|
||||||
&material_pipeline,
|
&material_pipeline,
|
||||||
|
|
|
@ -462,6 +462,9 @@ pub struct StandardMaterial {
|
||||||
/// Default is `16.0`.
|
/// Default is `16.0`.
|
||||||
pub max_parallax_layer_count: f32,
|
pub max_parallax_layer_count: f32,
|
||||||
|
|
||||||
|
/// The exposure (brightness) level of the lightmap, if present.
|
||||||
|
pub lightmap_exposure: f32,
|
||||||
|
|
||||||
/// Render method used for opaque materials. (Where `alpha_mode` is [`AlphaMode::Opaque`] or [`AlphaMode::Mask`])
|
/// Render method used for opaque materials. (Where `alpha_mode` is [`AlphaMode::Opaque`] or [`AlphaMode::Mask`])
|
||||||
pub opaque_render_method: OpaqueRendererMethod,
|
pub opaque_render_method: OpaqueRendererMethod,
|
||||||
|
|
||||||
|
@ -513,6 +516,7 @@ impl Default for StandardMaterial {
|
||||||
depth_map: None,
|
depth_map: None,
|
||||||
parallax_depth_scale: 0.1,
|
parallax_depth_scale: 0.1,
|
||||||
max_parallax_layer_count: 16.0,
|
max_parallax_layer_count: 16.0,
|
||||||
|
lightmap_exposure: 1.0,
|
||||||
parallax_mapping_method: ParallaxMappingMethod::Occlusion,
|
parallax_mapping_method: ParallaxMappingMethod::Occlusion,
|
||||||
opaque_render_method: OpaqueRendererMethod::Auto,
|
opaque_render_method: OpaqueRendererMethod::Auto,
|
||||||
deferred_lighting_pass_id: DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID,
|
deferred_lighting_pass_id: DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID,
|
||||||
|
@ -621,6 +625,8 @@ pub struct StandardMaterialUniform {
|
||||||
/// If your `parallax_depth_scale` is >0.1 and you are seeing jaggy edges,
|
/// If your `parallax_depth_scale` is >0.1 and you are seeing jaggy edges,
|
||||||
/// increase this value. However, this incurs a performance cost.
|
/// increase this value. However, this incurs a performance cost.
|
||||||
pub max_parallax_layer_count: f32,
|
pub max_parallax_layer_count: f32,
|
||||||
|
/// The exposure (brightness) level of the lightmap, if present.
|
||||||
|
pub lightmap_exposure: f32,
|
||||||
/// Using [`ParallaxMappingMethod::Relief`], how many additional
|
/// Using [`ParallaxMappingMethod::Relief`], how many additional
|
||||||
/// steps to use at most to find the depth value.
|
/// steps to use at most to find the depth value.
|
||||||
pub max_relief_mapping_search_steps: u32,
|
pub max_relief_mapping_search_steps: u32,
|
||||||
|
@ -720,6 +726,7 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
|
||||||
alpha_cutoff,
|
alpha_cutoff,
|
||||||
parallax_depth_scale: self.parallax_depth_scale,
|
parallax_depth_scale: self.parallax_depth_scale,
|
||||||
max_parallax_layer_count: self.max_parallax_layer_count,
|
max_parallax_layer_count: self.max_parallax_layer_count,
|
||||||
|
lightmap_exposure: self.lightmap_exposure,
|
||||||
max_relief_mapping_search_steps: self.parallax_mapping_method.max_steps(),
|
max_relief_mapping_search_steps: self.parallax_mapping_method.max_steps(),
|
||||||
deferred_lighting_pass_id: self.deferred_lighting_pass_id as u32,
|
deferred_lighting_pass_id: self.deferred_lighting_pass_id as u32,
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,6 +366,11 @@ where
|
||||||
vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(1));
|
vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if layout.contains(Mesh::ATTRIBUTE_UV_1) {
|
||||||
|
shader_defs.push("VERTEX_UVS_B".into());
|
||||||
|
vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(2));
|
||||||
|
}
|
||||||
|
|
||||||
if key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) {
|
if key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) {
|
||||||
shader_defs.push("NORMAL_PREPASS".into());
|
shader_defs.push("NORMAL_PREPASS".into());
|
||||||
}
|
}
|
||||||
|
@ -374,11 +379,11 @@ where
|
||||||
.mesh_key
|
.mesh_key
|
||||||
.intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::DEFERRED_PREPASS)
|
.intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::DEFERRED_PREPASS)
|
||||||
{
|
{
|
||||||
vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(2));
|
vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(3));
|
||||||
shader_defs.push("NORMAL_PREPASS_OR_DEFERRED_PREPASS".into());
|
shader_defs.push("NORMAL_PREPASS_OR_DEFERRED_PREPASS".into());
|
||||||
if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
|
if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
|
||||||
shader_defs.push("VERTEX_TANGENTS".into());
|
shader_defs.push("VERTEX_TANGENTS".into());
|
||||||
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
|
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(4));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,7 +400,7 @@ where
|
||||||
|
|
||||||
if layout.contains(Mesh::ATTRIBUTE_COLOR) {
|
if layout.contains(Mesh::ATTRIBUTE_COLOR) {
|
||||||
shader_defs.push("VERTEX_COLORS".into());
|
shader_defs.push("VERTEX_COLORS".into());
|
||||||
vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(6));
|
vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(7));
|
||||||
}
|
}
|
||||||
|
|
||||||
if key
|
if key
|
||||||
|
@ -681,6 +686,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
||||||
render_mesh_instances: Res<RenderMeshInstances>,
|
render_mesh_instances: Res<RenderMeshInstances>,
|
||||||
render_materials: Res<RenderMaterials<M>>,
|
render_materials: Res<RenderMaterials<M>>,
|
||||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||||
|
render_lightmaps: Res<RenderLightmaps>,
|
||||||
mut views: Query<
|
mut views: Query<
|
||||||
(
|
(
|
||||||
&ExtractedView,
|
&ExtractedView,
|
||||||
|
@ -793,6 +799,18 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
||||||
mesh_key |= MeshPipelineKey::DEFERRED_PREPASS;
|
mesh_key |= MeshPipelineKey::DEFERRED_PREPASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Even though we don't use the lightmap in the prepass, the
|
||||||
|
// `SetMeshBindGroup` render command will bind the data for it. So
|
||||||
|
// we need to include the appropriate flag in the mesh pipeline key
|
||||||
|
// to ensure that the necessary bind group layout entries are
|
||||||
|
// present.
|
||||||
|
if render_lightmaps
|
||||||
|
.render_lightmaps
|
||||||
|
.contains_key(visible_entity)
|
||||||
|
{
|
||||||
|
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
|
||||||
|
}
|
||||||
|
|
||||||
let pipeline_id = pipelines.specialize(
|
let pipeline_id = pipelines.specialize(
|
||||||
&pipeline_cache,
|
&pipeline_cache,
|
||||||
&prepass_pipeline,
|
&prepass_pipeline,
|
||||||
|
|
|
@ -62,6 +62,10 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
|
||||||
out.uv = vertex.uv;
|
out.uv = vertex.uv;
|
||||||
#endif // VERTEX_UVS
|
#endif // VERTEX_UVS
|
||||||
|
|
||||||
|
#ifdef VERTEX_UVS_B
|
||||||
|
out.uv_b = vertex.uv_b;
|
||||||
|
#endif // VERTEX_UVS_B
|
||||||
|
|
||||||
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
||||||
#ifdef SKINNED
|
#ifdef SKINNED
|
||||||
out.world_normal = skinning::skin_normals(model, vertex.normal);
|
out.world_normal = skinning::skin_normals(model, vertex.normal);
|
||||||
|
|
|
@ -10,20 +10,24 @@ struct Vertex {
|
||||||
@location(1) uv: vec2<f32>,
|
@location(1) uv: vec2<f32>,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef VERTEX_UVS_B
|
||||||
|
@location(2) uv_b: vec2<f32>,
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
||||||
@location(2) normal: vec3<f32>,
|
@location(3) normal: vec3<f32>,
|
||||||
#ifdef VERTEX_TANGENTS
|
#ifdef VERTEX_TANGENTS
|
||||||
@location(3) tangent: vec4<f32>,
|
@location(4) tangent: vec4<f32>,
|
||||||
#endif
|
#endif
|
||||||
#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
||||||
|
|
||||||
#ifdef SKINNED
|
#ifdef SKINNED
|
||||||
@location(4) joint_indices: vec4<u32>,
|
@location(5) joint_indices: vec4<u32>,
|
||||||
@location(5) joint_weights: vec4<f32>,
|
@location(6) joint_weights: vec4<f32>,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef VERTEX_COLORS
|
#ifdef VERTEX_COLORS
|
||||||
@location(6) color: vec4<f32>,
|
@location(7) color: vec4<f32>,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef MORPH_TARGETS
|
#ifdef MORPH_TARGETS
|
||||||
|
@ -40,27 +44,31 @@ struct VertexOutput {
|
||||||
@location(0) uv: vec2<f32>,
|
@location(0) uv: vec2<f32>,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef VERTEX_UVS_B
|
||||||
|
@location(1) uv_b: vec2<f32>,
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
||||||
@location(1) world_normal: vec3<f32>,
|
@location(2) world_normal: vec3<f32>,
|
||||||
#ifdef VERTEX_TANGENTS
|
#ifdef VERTEX_TANGENTS
|
||||||
@location(2) world_tangent: vec4<f32>,
|
@location(3) world_tangent: vec4<f32>,
|
||||||
#endif
|
#endif
|
||||||
#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
||||||
|
|
||||||
@location(3) world_position: vec4<f32>,
|
@location(4) world_position: vec4<f32>,
|
||||||
#ifdef MOTION_VECTOR_PREPASS
|
#ifdef MOTION_VECTOR_PREPASS
|
||||||
@location(4) previous_world_position: vec4<f32>,
|
@location(5) previous_world_position: vec4<f32>,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef DEPTH_CLAMP_ORTHO
|
#ifdef DEPTH_CLAMP_ORTHO
|
||||||
@location(5) clip_position_unclamped: vec4<f32>,
|
@location(6) clip_position_unclamped: vec4<f32>,
|
||||||
#endif // DEPTH_CLAMP_ORTHO
|
#endif // DEPTH_CLAMP_ORTHO
|
||||||
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
|
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
|
||||||
@location(6) instance_index: u32,
|
@location(7) instance_index: u32,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef VERTEX_COLORS
|
#ifdef VERTEX_COLORS
|
||||||
@location(7) color: vec4<f32>,
|
@location(8) color: vec4<f32>,
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,9 @@ struct Vertex {
|
||||||
#ifdef VERTEX_UVS
|
#ifdef VERTEX_UVS
|
||||||
@location(2) uv: vec2<f32>,
|
@location(2) uv: vec2<f32>,
|
||||||
#endif
|
#endif
|
||||||
// (Alternate UVs are at location 3, but they're currently unused here.)
|
#ifdef VERTEX_UVS_B
|
||||||
|
@location(3) uv_b: vec2<f32>,
|
||||||
|
#endif
|
||||||
#ifdef VERTEX_TANGENTS
|
#ifdef VERTEX_TANGENTS
|
||||||
@location(4) tangent: vec4<f32>,
|
@location(4) tangent: vec4<f32>,
|
||||||
#endif
|
#endif
|
||||||
|
@ -36,14 +38,17 @@ struct VertexOutput {
|
||||||
#ifdef VERTEX_UVS
|
#ifdef VERTEX_UVS
|
||||||
@location(2) uv: vec2<f32>,
|
@location(2) uv: vec2<f32>,
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef VERTEX_UVS_B
|
||||||
|
@location(3) uv_b: vec2<f32>,
|
||||||
|
#endif
|
||||||
#ifdef VERTEX_TANGENTS
|
#ifdef VERTEX_TANGENTS
|
||||||
@location(3) world_tangent: vec4<f32>,
|
@location(4) world_tangent: vec4<f32>,
|
||||||
#endif
|
#endif
|
||||||
#ifdef VERTEX_COLORS
|
#ifdef VERTEX_COLORS
|
||||||
@location(4) color: vec4<f32>,
|
@location(5) color: vec4<f32>,
|
||||||
#endif
|
#endif
|
||||||
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
|
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
|
||||||
@location(5) @interpolate(flat) instance_index: u32,
|
@location(6) @interpolate(flat) instance_index: u32,
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ use bevy_ecs::{
|
||||||
query::{QueryItem, ROQueryItem},
|
query::{QueryItem, ROQueryItem},
|
||||||
system::{lifetimeless::*, SystemParamItem, SystemState},
|
system::{lifetimeless::*, SystemParamItem, SystemState},
|
||||||
};
|
};
|
||||||
use bevy_math::{Affine3, Vec4};
|
use bevy_math::{Affine3, Rect, UVec2, Vec4};
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
batching::{
|
batching::{
|
||||||
batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData,
|
batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData,
|
||||||
|
@ -26,7 +26,7 @@ use bevy_render::{
|
||||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||||
};
|
};
|
||||||
use bevy_transform::components::GlobalTransform;
|
use bevy_transform::components::GlobalTransform;
|
||||||
use bevy_utils::{tracing::error, EntityHashMap, HashMap, Hashed};
|
use bevy_utils::{tracing::error, EntityHashMap, Entry, HashMap, Hashed};
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use thread_local::ThreadLocal;
|
use thread_local::ThreadLocal;
|
||||||
|
|
||||||
|
@ -195,6 +195,16 @@ pub struct MeshUniform {
|
||||||
// Affine 4x3 matrices transposed to 3x4
|
// Affine 4x3 matrices transposed to 3x4
|
||||||
pub transform: [Vec4; 3],
|
pub transform: [Vec4; 3],
|
||||||
pub previous_transform: [Vec4; 3],
|
pub previous_transform: [Vec4; 3],
|
||||||
|
// Four 16-bit unsigned normalized UV values packed into a `UVec2`:
|
||||||
|
//
|
||||||
|
// <--- MSB LSB --->
|
||||||
|
// +---- min v ----+ +---- min u ----+
|
||||||
|
// lightmap_uv_rect.x: vvvvvvvv vvvvvvvv uuuuuuuu uuuuuuuu,
|
||||||
|
// +---- max v ----+ +---- max u ----+
|
||||||
|
// lightmap_uv_rect.y: VVVVVVVV VVVVVVVV UUUUUUUU UUUUUUUU,
|
||||||
|
//
|
||||||
|
// (MSB: most significant bit; LSB: least significant bit.)
|
||||||
|
pub lightmap_uv_rect: UVec2,
|
||||||
// 3x3 matrix packed in mat2x4 and f32 as:
|
// 3x3 matrix packed in mat2x4 and f32 as:
|
||||||
// [0].xyz, [1].x,
|
// [0].xyz, [1].x,
|
||||||
// [1].yz, [2].xy
|
// [1].yz, [2].xy
|
||||||
|
@ -204,13 +214,14 @@ pub struct MeshUniform {
|
||||||
pub flags: u32,
|
pub flags: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&MeshTransforms> for MeshUniform {
|
impl MeshUniform {
|
||||||
fn from(mesh_transforms: &MeshTransforms) -> Self {
|
fn new(mesh_transforms: &MeshTransforms, maybe_lightmap_uv_rect: Option<Rect>) -> Self {
|
||||||
let (inverse_transpose_model_a, inverse_transpose_model_b) =
|
let (inverse_transpose_model_a, inverse_transpose_model_b) =
|
||||||
mesh_transforms.transform.inverse_transpose_3x3();
|
mesh_transforms.transform.inverse_transpose_3x3();
|
||||||
Self {
|
Self {
|
||||||
transform: mesh_transforms.transform.to_transpose(),
|
transform: mesh_transforms.transform.to_transpose(),
|
||||||
previous_transform: mesh_transforms.previous_transform.to_transpose(),
|
previous_transform: mesh_transforms.previous_transform.to_transpose(),
|
||||||
|
lightmap_uv_rect: lightmap::pack_lightmap_uv_rect(maybe_lightmap_uv_rect),
|
||||||
inverse_transpose_model_a,
|
inverse_transpose_model_a,
|
||||||
inverse_transpose_model_b,
|
inverse_transpose_model_b,
|
||||||
flags: mesh_transforms.flags,
|
flags: mesh_transforms.flags,
|
||||||
|
@ -447,24 +458,34 @@ impl MeshPipeline {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GetBatchData for MeshPipeline {
|
impl GetBatchData for MeshPipeline {
|
||||||
type Param = SRes<RenderMeshInstances>;
|
type Param = (SRes<RenderMeshInstances>, SRes<RenderLightmaps>);
|
||||||
type Data = Entity;
|
type Data = Entity;
|
||||||
type Filter = With<Mesh3d>;
|
type Filter = With<Mesh3d>;
|
||||||
type CompareData = (MaterialBindGroupId, AssetId<Mesh>);
|
|
||||||
|
// The material bind group ID, the mesh ID, and the lightmap ID,
|
||||||
|
// respectively.
|
||||||
|
type CompareData = (MaterialBindGroupId, AssetId<Mesh>, Option<AssetId<Image>>);
|
||||||
|
|
||||||
type BufferData = MeshUniform;
|
type BufferData = MeshUniform;
|
||||||
|
|
||||||
fn get_batch_data(
|
fn get_batch_data(
|
||||||
mesh_instances: &SystemParamItem<Self::Param>,
|
(mesh_instances, lightmaps): &SystemParamItem<Self::Param>,
|
||||||
entity: &QueryItem<Self::Data>,
|
entity: &QueryItem<Self::Data>,
|
||||||
) -> (Self::BufferData, Option<Self::CompareData>) {
|
) -> (Self::BufferData, Option<Self::CompareData>) {
|
||||||
let mesh_instance = mesh_instances
|
let mesh_instance = mesh_instances
|
||||||
.get(entity)
|
.get(entity)
|
||||||
.expect("Failed to find render mesh instance");
|
.expect("Failed to find render mesh instance");
|
||||||
|
let maybe_lightmap = lightmaps.render_lightmaps.get(entity);
|
||||||
|
|
||||||
(
|
(
|
||||||
(&mesh_instance.transforms).into(),
|
MeshUniform::new(
|
||||||
|
&mesh_instance.transforms,
|
||||||
|
maybe_lightmap.map(|lightmap| lightmap.uv_rect),
|
||||||
|
),
|
||||||
mesh_instance.automatic_batching.then_some((
|
mesh_instance.automatic_batching.then_some((
|
||||||
mesh_instance.material_bind_group_id,
|
mesh_instance.material_bind_group_id,
|
||||||
mesh_instance.mesh_asset_id,
|
mesh_instance.mesh_asset_id,
|
||||||
|
maybe_lightmap.map(|lightmap| lightmap.image),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -492,6 +513,7 @@ bitflags::bitflags! {
|
||||||
const TEMPORAL_JITTER = 1 << 11;
|
const TEMPORAL_JITTER = 1 << 11;
|
||||||
const MORPH_TARGETS = 1 << 12;
|
const MORPH_TARGETS = 1 << 12;
|
||||||
const READS_VIEW_TRANSMISSION_TEXTURE = 1 << 13;
|
const READS_VIEW_TRANSMISSION_TEXTURE = 1 << 13;
|
||||||
|
const LIGHTMAPPED = 1 << 14;
|
||||||
const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state
|
const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state
|
||||||
const BLEND_OPAQUE = 0 << Self::BLEND_SHIFT_BITS; // ← Values are just sequential within the mask, and can range from 0 to 3
|
const BLEND_OPAQUE = 0 << Self::BLEND_SHIFT_BITS; // ← Values are just sequential within the mask, and can range from 0 to 3
|
||||||
const BLEND_PREMULTIPLIED_ALPHA = 1 << Self::BLEND_SHIFT_BITS; //
|
const BLEND_PREMULTIPLIED_ALPHA = 1 << Self::BLEND_SHIFT_BITS; //
|
||||||
|
@ -609,21 +631,23 @@ pub fn setup_morph_and_skinning_defs(
|
||||||
vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(offset + 1));
|
vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(offset + 1));
|
||||||
};
|
};
|
||||||
let is_morphed = key.intersects(MeshPipelineKey::MORPH_TARGETS);
|
let is_morphed = key.intersects(MeshPipelineKey::MORPH_TARGETS);
|
||||||
match (is_skinned(layout), is_morphed) {
|
let is_lightmapped = key.intersects(MeshPipelineKey::LIGHTMAPPED);
|
||||||
(true, false) => {
|
match (is_skinned(layout), is_morphed, is_lightmapped) {
|
||||||
|
(true, false, _) => {
|
||||||
add_skin_data();
|
add_skin_data();
|
||||||
mesh_layouts.skinned.clone()
|
mesh_layouts.skinned.clone()
|
||||||
}
|
}
|
||||||
(true, true) => {
|
(true, true, _) => {
|
||||||
add_skin_data();
|
add_skin_data();
|
||||||
shader_defs.push("MORPH_TARGETS".into());
|
shader_defs.push("MORPH_TARGETS".into());
|
||||||
mesh_layouts.morphed_skinned.clone()
|
mesh_layouts.morphed_skinned.clone()
|
||||||
}
|
}
|
||||||
(false, true) => {
|
(false, true, _) => {
|
||||||
shader_defs.push("MORPH_TARGETS".into());
|
shader_defs.push("MORPH_TARGETS".into());
|
||||||
mesh_layouts.morphed.clone()
|
mesh_layouts.morphed.clone()
|
||||||
}
|
}
|
||||||
(false, false) => mesh_layouts.model_only.clone(),
|
(false, false, true) => mesh_layouts.lightmapped.clone(),
|
||||||
|
(false, false, false) => mesh_layouts.model_only.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -659,7 +683,7 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
||||||
}
|
}
|
||||||
|
|
||||||
if layout.contains(Mesh::ATTRIBUTE_UV_1) {
|
if layout.contains(Mesh::ATTRIBUTE_UV_1) {
|
||||||
shader_defs.push("VERTEX_UVS_1".into());
|
shader_defs.push("VERTEX_UVS_B".into());
|
||||||
vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(3));
|
vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -810,6 +834,10 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
||||||
shader_defs.push("ENVIRONMENT_MAP".into());
|
shader_defs.push("ENVIRONMENT_MAP".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key.contains(MeshPipelineKey::LIGHTMAPPED) {
|
||||||
|
shader_defs.push("LIGHTMAP".into());
|
||||||
|
}
|
||||||
|
|
||||||
if key.contains(MeshPipelineKey::TEMPORAL_JITTER) {
|
if key.contains(MeshPipelineKey::TEMPORAL_JITTER) {
|
||||||
shader_defs.push("TEMPORAL_JITTER".into());
|
shader_defs.push("TEMPORAL_JITTER".into());
|
||||||
}
|
}
|
||||||
|
@ -922,36 +950,44 @@ pub struct MeshBindGroups {
|
||||||
model_only: Option<BindGroup>,
|
model_only: Option<BindGroup>,
|
||||||
skinned: Option<BindGroup>,
|
skinned: Option<BindGroup>,
|
||||||
morph_targets: HashMap<AssetId<Mesh>, BindGroup>,
|
morph_targets: HashMap<AssetId<Mesh>, BindGroup>,
|
||||||
|
lightmaps: HashMap<AssetId<Image>, BindGroup>,
|
||||||
}
|
}
|
||||||
impl MeshBindGroups {
|
impl MeshBindGroups {
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.model_only = None;
|
self.model_only = None;
|
||||||
self.skinned = None;
|
self.skinned = None;
|
||||||
self.morph_targets.clear();
|
self.morph_targets.clear();
|
||||||
|
self.lightmaps.clear();
|
||||||
}
|
}
|
||||||
/// Get the `BindGroup` for `GpuMesh` with given `handle_id`.
|
/// Get the `BindGroup` for `GpuMesh` with given `handle_id` and lightmap
|
||||||
|
/// key `lightmap`.
|
||||||
pub fn get(
|
pub fn get(
|
||||||
&self,
|
&self,
|
||||||
asset_id: AssetId<Mesh>,
|
asset_id: AssetId<Mesh>,
|
||||||
|
lightmap: Option<AssetId<Image>>,
|
||||||
is_skinned: bool,
|
is_skinned: bool,
|
||||||
morph: bool,
|
morph: bool,
|
||||||
) -> Option<&BindGroup> {
|
) -> Option<&BindGroup> {
|
||||||
match (is_skinned, morph) {
|
match (is_skinned, morph, lightmap) {
|
||||||
(_, true) => self.morph_targets.get(&asset_id),
|
(_, true, _) => self.morph_targets.get(&asset_id),
|
||||||
(true, false) => self.skinned.as_ref(),
|
(true, false, _) => self.skinned.as_ref(),
|
||||||
(false, false) => self.model_only.as_ref(),
|
(false, false, Some(lightmap)) => self.lightmaps.get(&lightmap),
|
||||||
|
(false, false, None) => self.model_only.as_ref(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn prepare_mesh_bind_group(
|
pub fn prepare_mesh_bind_group(
|
||||||
meshes: Res<RenderAssets<Mesh>>,
|
meshes: Res<RenderAssets<Mesh>>,
|
||||||
|
images: Res<RenderAssets<Image>>,
|
||||||
mut groups: ResMut<MeshBindGroups>,
|
mut groups: ResMut<MeshBindGroups>,
|
||||||
mesh_pipeline: Res<MeshPipeline>,
|
mesh_pipeline: Res<MeshPipeline>,
|
||||||
render_device: Res<RenderDevice>,
|
render_device: Res<RenderDevice>,
|
||||||
mesh_uniforms: Res<GpuArrayBuffer<MeshUniform>>,
|
mesh_uniforms: Res<GpuArrayBuffer<MeshUniform>>,
|
||||||
skins_uniform: Res<SkinUniform>,
|
skins_uniform: Res<SkinUniform>,
|
||||||
weights_uniform: Res<MorphUniform>,
|
weights_uniform: Res<MorphUniform>,
|
||||||
|
render_lightmaps: Res<RenderLightmaps>,
|
||||||
) {
|
) {
|
||||||
groups.reset();
|
groups.reset();
|
||||||
let layouts = &mesh_pipeline.mesh_layouts;
|
let layouts = &mesh_pipeline.mesh_layouts;
|
||||||
|
@ -977,6 +1013,15 @@ pub fn prepare_mesh_bind_group(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create lightmap bindgroups.
|
||||||
|
for &image_id in &render_lightmaps.all_lightmap_images {
|
||||||
|
if let (Entry::Vacant(entry), Some(image)) =
|
||||||
|
(groups.lightmaps.entry(image_id), images.get(image_id))
|
||||||
|
{
|
||||||
|
entry.insert(layouts.lightmapped(&render_device, &model, image));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SetMeshViewBindGroup<const I: usize>;
|
pub struct SetMeshViewBindGroup<const I: usize>;
|
||||||
|
@ -1018,6 +1063,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
|
||||||
SRes<RenderMeshInstances>,
|
SRes<RenderMeshInstances>,
|
||||||
SRes<SkinIndices>,
|
SRes<SkinIndices>,
|
||||||
SRes<MorphIndices>,
|
SRes<MorphIndices>,
|
||||||
|
SRes<RenderLightmaps>,
|
||||||
);
|
);
|
||||||
type ViewData = ();
|
type ViewData = ();
|
||||||
type ItemData = ();
|
type ItemData = ();
|
||||||
|
@ -1027,7 +1073,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
|
||||||
item: &P,
|
item: &P,
|
||||||
_view: (),
|
_view: (),
|
||||||
_item_query: (),
|
_item_query: (),
|
||||||
(bind_groups, mesh_instances, skin_indices, morph_indices): SystemParamItem<
|
(bind_groups, mesh_instances, skin_indices, morph_indices, lightmaps): SystemParamItem<
|
||||||
'w,
|
'w,
|
||||||
'_,
|
'_,
|
||||||
Self::Param,
|
Self::Param,
|
||||||
|
@ -1050,7 +1096,14 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
|
||||||
let is_skinned = skin_index.is_some();
|
let is_skinned = skin_index.is_some();
|
||||||
let is_morphed = morph_index.is_some();
|
let is_morphed = morph_index.is_some();
|
||||||
|
|
||||||
let Some(bind_group) = bind_groups.get(mesh.mesh_asset_id, is_skinned, is_morphed) else {
|
let lightmap = lightmaps
|
||||||
|
.render_lightmaps
|
||||||
|
.get(entity)
|
||||||
|
.map(|render_lightmap| render_lightmap.image);
|
||||||
|
|
||||||
|
let Some(bind_group) =
|
||||||
|
bind_groups.get(mesh.mesh_asset_id, lightmap, is_skinned, is_morphed)
|
||||||
|
else {
|
||||||
error!(
|
error!(
|
||||||
"The MeshBindGroups resource wasn't set in the render phase. \
|
"The MeshBindGroups resource wasn't set in the render phase. \
|
||||||
It should be set by the queue_mesh_bind_group system.\n\
|
It should be set by the queue_mesh_bind_group system.\n\
|
||||||
|
|
|
@ -68,6 +68,10 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
|
||||||
out.uv = vertex.uv;
|
out.uv = vertex.uv;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef VERTEX_UVS_B
|
||||||
|
out.uv_b = vertex.uv_b;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef VERTEX_TANGENTS
|
#ifdef VERTEX_TANGENTS
|
||||||
out.world_tangent = mesh_functions::mesh_tangent_local_to_world(
|
out.world_tangent = mesh_functions::mesh_tangent_local_to_world(
|
||||||
model,
|
model,
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
//! Bind group layout related definitions for the mesh pipeline.
|
//! Bind group layout related definitions for the mesh pipeline.
|
||||||
|
|
||||||
use bevy_math::Mat4;
|
use bevy_math::Mat4;
|
||||||
use bevy_render::{mesh::morph::MAX_MORPH_WEIGHTS, render_resource::*, renderer::RenderDevice};
|
use bevy_render::{
|
||||||
|
mesh::morph::MAX_MORPH_WEIGHTS, render_resource::*, renderer::RenderDevice, texture::GpuImage,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::render::skin::MAX_JOINTS;
|
use crate::render::skin::MAX_JOINTS;
|
||||||
|
|
||||||
|
@ -17,9 +19,9 @@ mod layout_entry {
|
||||||
use crate::MeshUniform;
|
use crate::MeshUniform;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
render_resource::{
|
render_resource::{
|
||||||
binding_types::{texture_3d, uniform_buffer_sized},
|
binding_types::{sampler, texture_2d, texture_3d, uniform_buffer_sized},
|
||||||
BindGroupLayoutEntryBuilder, BufferSize, GpuArrayBuffer, ShaderStages,
|
BindGroupLayoutEntryBuilder, BufferSize, GpuArrayBuffer, SamplerBindingType,
|
||||||
TextureSampleType,
|
ShaderStages, TextureSampleType,
|
||||||
},
|
},
|
||||||
renderer::RenderDevice,
|
renderer::RenderDevice,
|
||||||
};
|
};
|
||||||
|
@ -37,6 +39,12 @@ mod layout_entry {
|
||||||
pub(super) fn targets() -> BindGroupLayoutEntryBuilder {
|
pub(super) fn targets() -> BindGroupLayoutEntryBuilder {
|
||||||
texture_3d(TextureSampleType::Float { filterable: false })
|
texture_3d(TextureSampleType::Float { filterable: false })
|
||||||
}
|
}
|
||||||
|
pub(super) fn lightmaps_texture_view() -> BindGroupLayoutEntryBuilder {
|
||||||
|
texture_2d(TextureSampleType::Float { filterable: true }).visibility(ShaderStages::FRAGMENT)
|
||||||
|
}
|
||||||
|
pub(super) fn lightmaps_sampler() -> BindGroupLayoutEntryBuilder {
|
||||||
|
sampler(SamplerBindingType::Filtering).visibility(ShaderStages::FRAGMENT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Individual [`BindGroupEntry`]
|
/// Individual [`BindGroupEntry`]
|
||||||
|
@ -44,7 +52,7 @@ mod layout_entry {
|
||||||
mod entry {
|
mod entry {
|
||||||
use super::{JOINT_BUFFER_SIZE, MORPH_BUFFER_SIZE};
|
use super::{JOINT_BUFFER_SIZE, MORPH_BUFFER_SIZE};
|
||||||
use bevy_render::render_resource::{
|
use bevy_render::render_resource::{
|
||||||
BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferSize, TextureView,
|
BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferSize, Sampler, TextureView,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn entry(binding: u32, size: u64, buffer: &Buffer) -> BindGroupEntry {
|
fn entry(binding: u32, size: u64, buffer: &Buffer) -> BindGroupEntry {
|
||||||
|
@ -72,6 +80,18 @@ mod entry {
|
||||||
resource: BindingResource::TextureView(texture),
|
resource: BindingResource::TextureView(texture),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub(super) fn lightmaps_texture_view(binding: u32, texture: &TextureView) -> BindGroupEntry {
|
||||||
|
BindGroupEntry {
|
||||||
|
binding,
|
||||||
|
resource: BindingResource::TextureView(texture),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(super) fn lightmaps_sampler(binding: u32, sampler: &Sampler) -> BindGroupEntry {
|
||||||
|
BindGroupEntry {
|
||||||
|
binding,
|
||||||
|
resource: BindingResource::Sampler(sampler),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All possible [`BindGroupLayout`]s in bevy's default mesh shader (`mesh.wgsl`).
|
/// All possible [`BindGroupLayout`]s in bevy's default mesh shader (`mesh.wgsl`).
|
||||||
|
@ -80,6 +100,9 @@ pub struct MeshLayouts {
|
||||||
/// The mesh model uniform (transform) and nothing else.
|
/// The mesh model uniform (transform) and nothing else.
|
||||||
pub model_only: BindGroupLayout,
|
pub model_only: BindGroupLayout,
|
||||||
|
|
||||||
|
/// Includes the lightmap texture and uniform.
|
||||||
|
pub lightmapped: BindGroupLayout,
|
||||||
|
|
||||||
/// Also includes the uniform for skinning
|
/// Also includes the uniform for skinning
|
||||||
pub skinned: BindGroupLayout,
|
pub skinned: BindGroupLayout,
|
||||||
|
|
||||||
|
@ -102,6 +125,7 @@ impl MeshLayouts {
|
||||||
pub fn new(render_device: &RenderDevice) -> Self {
|
pub fn new(render_device: &RenderDevice) -> Self {
|
||||||
MeshLayouts {
|
MeshLayouts {
|
||||||
model_only: Self::model_only_layout(render_device),
|
model_only: Self::model_only_layout(render_device),
|
||||||
|
lightmapped: Self::lightmapped_layout(render_device),
|
||||||
skinned: Self::skinned_layout(render_device),
|
skinned: Self::skinned_layout(render_device),
|
||||||
morphed: Self::morphed_layout(render_device),
|
morphed: Self::morphed_layout(render_device),
|
||||||
morphed_skinned: Self::morphed_skinned_layout(render_device),
|
morphed_skinned: Self::morphed_skinned_layout(render_device),
|
||||||
|
@ -158,6 +182,19 @@ impl MeshLayouts {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fn lightmapped_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
||||||
|
render_device.create_bind_group_layout(
|
||||||
|
"lightmapped_mesh_layout",
|
||||||
|
&BindGroupLayoutEntries::with_indices(
|
||||||
|
ShaderStages::VERTEX,
|
||||||
|
(
|
||||||
|
(0, layout_entry::model(render_device)),
|
||||||
|
(4, layout_entry::lightmaps_texture_view()),
|
||||||
|
(5, layout_entry::lightmaps_sampler()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- BindGroup methods ----------
|
// ---------- BindGroup methods ----------
|
||||||
|
|
||||||
|
@ -168,6 +205,22 @@ impl MeshLayouts {
|
||||||
&[entry::model(0, model.clone())],
|
&[entry::model(0, model.clone())],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
pub fn lightmapped(
|
||||||
|
&self,
|
||||||
|
render_device: &RenderDevice,
|
||||||
|
model: &BindingResource,
|
||||||
|
lightmap: &GpuImage,
|
||||||
|
) -> BindGroup {
|
||||||
|
render_device.create_bind_group(
|
||||||
|
"lightmapped_mesh_bind_group",
|
||||||
|
&self.lightmapped,
|
||||||
|
&[
|
||||||
|
entry::model(0, model.clone()),
|
||||||
|
entry::lightmaps_texture_view(4, &lightmap.texture_view),
|
||||||
|
entry::lightmaps_sampler(5, &lightmap.sampler),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
pub fn skinned(
|
pub fn skinned(
|
||||||
&self,
|
&self,
|
||||||
render_device: &RenderDevice,
|
render_device: &RenderDevice,
|
||||||
|
|
|
@ -5,6 +5,7 @@ struct Mesh {
|
||||||
// Use bevy_render::maths::affine_to_square to unpack
|
// Use bevy_render::maths::affine_to_square to unpack
|
||||||
model: mat3x4<f32>,
|
model: mat3x4<f32>,
|
||||||
previous_model: mat3x4<f32>,
|
previous_model: mat3x4<f32>,
|
||||||
|
lightmap_uv_rect: vec2<u32>,
|
||||||
// 3x3 matrix packed in mat2x4 and f32 as:
|
// 3x3 matrix packed in mat2x4 and f32 as:
|
||||||
// [0].xyz, [1].x,
|
// [0].xyz, [1].x,
|
||||||
// [1].yz, [2].xy
|
// [1].yz, [2].xy
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
mesh_bindings::mesh,
|
mesh_bindings::mesh,
|
||||||
mesh_view_bindings::view,
|
mesh_view_bindings::view,
|
||||||
parallax_mapping::parallaxed_uv,
|
parallax_mapping::parallaxed_uv,
|
||||||
|
lightmap::lightmap,
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
|
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
|
||||||
|
@ -191,6 +192,13 @@ fn pbr_input_from_standard_material(
|
||||||
view.mip_bias,
|
view.mip_bias,
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LIGHTMAP
|
||||||
|
pbr_input.lightmap_light = lightmap(
|
||||||
|
in.uv_b,
|
||||||
|
pbr_bindings::material.lightmap_exposure,
|
||||||
|
in.instance_index);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return pbr_input;
|
return pbr_input;
|
||||||
|
|
|
@ -358,6 +358,10 @@ fn apply_pbr_lighting(
|
||||||
let specular_transmitted_environment_light = vec3<f32>(0.0);
|
let specular_transmitted_environment_light = vec3<f32>(0.0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LIGHTMAP
|
||||||
|
indirect_light += in.lightmap_light * diffuse_color;
|
||||||
|
#endif
|
||||||
|
|
||||||
let emissive_light = emissive.rgb * output_color.a;
|
let emissive_light = emissive.rgb * output_color.a;
|
||||||
|
|
||||||
if specular_transmission > 0.0 {
|
if specular_transmission > 0.0 {
|
||||||
|
|
|
@ -17,6 +17,7 @@ struct StandardMaterial {
|
||||||
alpha_cutoff: f32,
|
alpha_cutoff: f32,
|
||||||
parallax_depth_scale: f32,
|
parallax_depth_scale: f32,
|
||||||
max_parallax_layer_count: f32,
|
max_parallax_layer_count: f32,
|
||||||
|
lightmap_exposure: f32,
|
||||||
max_relief_mapping_search_steps: u32,
|
max_relief_mapping_search_steps: u32,
|
||||||
/// ID for specifying which deferred lighting pass should be used for rendering this material, if any.
|
/// ID for specifying which deferred lighting pass should be used for rendering this material, if any.
|
||||||
deferred_lighting_pass_id: u32,
|
deferred_lighting_pass_id: u32,
|
||||||
|
@ -90,6 +91,7 @@ struct PbrInput {
|
||||||
// Normalized view vector in world space, pointing from the fragment world position toward the
|
// Normalized view vector in world space, pointing from the fragment world position toward the
|
||||||
// view world position
|
// view world position
|
||||||
V: vec3<f32>,
|
V: vec3<f32>,
|
||||||
|
lightmap_light: vec3<f32>,
|
||||||
is_orthographic: bool,
|
is_orthographic: bool,
|
||||||
flags: u32,
|
flags: u32,
|
||||||
};
|
};
|
||||||
|
@ -110,6 +112,8 @@ fn pbr_input_new() -> PbrInput {
|
||||||
pbr_input.N = vec3<f32>(0.0, 0.0, 1.0);
|
pbr_input.N = vec3<f32>(0.0, 0.0, 1.0);
|
||||||
pbr_input.V = vec3<f32>(1.0, 0.0, 0.0);
|
pbr_input.V = vec3<f32>(1.0, 0.0, 0.0);
|
||||||
|
|
||||||
|
pbr_input.lightmap_light = vec3<f32>(0.0);
|
||||||
|
|
||||||
pbr_input.flags = 0u;
|
pbr_input.flags = 0u;
|
||||||
|
|
||||||
return pbr_input;
|
return pbr_input;
|
||||||
|
|
60
examples/3d/lightmaps.rs
Normal file
60
examples/3d/lightmaps.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
//! Rendering a scene with baked lightmaps.
|
||||||
|
|
||||||
|
use bevy::pbr::Lightmap;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.insert_resource(AmbientLight {
|
||||||
|
color: Color::WHITE,
|
||||||
|
brightness: 0.2,
|
||||||
|
})
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(Update, add_lightmaps_to_meshes)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
commands.spawn(SceneBundle {
|
||||||
|
scene: asset_server.load("models/CornellBox/CornellBox.glb#Scene0"),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.spawn(Camera3dBundle {
|
||||||
|
transform: Transform::from_xyz(-278.0, 273.0, 800.0),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_lightmaps_to_meshes(
|
||||||
|
mut commands: Commands,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
meshes: Query<(Entity, &Name), (With<Handle<Mesh>>, Without<Lightmap>)>,
|
||||||
|
) {
|
||||||
|
for (entity, name) in meshes.iter() {
|
||||||
|
if &**name == "large_box" {
|
||||||
|
commands.entity(entity).insert(Lightmap {
|
||||||
|
image: asset_server.load("lightmaps/CornellBox-Large.zstd.ktx2"),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if &**name == "small_box" {
|
||||||
|
commands.entity(entity).insert(Lightmap {
|
||||||
|
image: asset_server.load("lightmaps/CornellBox-Small.zstd.ktx2"),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.starts_with("cornell_box") {
|
||||||
|
commands.entity(entity).insert(Lightmap {
|
||||||
|
image: asset_server.load("lightmaps/CornellBox-Box.zstd.ktx2"),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -126,6 +126,7 @@ Example | Description
|
||||||
[Fog](../examples/3d/fog.rs) | A scene showcasing the distance fog effect
|
[Fog](../examples/3d/fog.rs) | A scene showcasing the distance fog effect
|
||||||
[Generate Custom Mesh](../examples/3d/generate_custom_mesh.rs) | Simple showcase of how to generate a custom mesh with a custom texture
|
[Generate Custom Mesh](../examples/3d/generate_custom_mesh.rs) | Simple showcase of how to generate a custom mesh with a custom texture
|
||||||
[Lighting](../examples/3d/lighting.rs) | Illustrates various lighting options in a simple scene
|
[Lighting](../examples/3d/lighting.rs) | Illustrates various lighting options in a simple scene
|
||||||
|
[Lightmaps](../examples/3d/lightmaps.rs) | Rendering a scene with baked lightmaps
|
||||||
[Lines](../examples/3d/lines.rs) | Create a custom material to draw 3d lines
|
[Lines](../examples/3d/lines.rs) | Create a custom material to draw 3d lines
|
||||||
[Load glTF](../examples/3d/load_gltf.rs) | Loads and renders a glTF file as a scene
|
[Load glTF](../examples/3d/load_gltf.rs) | Loads and renders a glTF file as a scene
|
||||||
[Orthographic View](../examples/3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look in games or CAD applications)
|
[Orthographic View](../examples/3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look in games or CAD applications)
|
||||||
|
|
Loading…
Reference in a new issue