mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 12:13:25 +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"
|
||||
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]]
|
||||
name = "no_prepass"
|
||||
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 fog;
|
||||
mod light;
|
||||
mod lightmap;
|
||||
mod material;
|
||||
mod parallax;
|
||||
mod pbr_material;
|
||||
|
@ -20,6 +21,7 @@ pub use environment_map::EnvironmentMapLight;
|
|||
pub use extended_material::*;
|
||||
pub use fog::*;
|
||||
pub use light::*;
|
||||
pub use lightmap::*;
|
||||
pub use material::*;
|
||||
pub use parallax::*;
|
||||
pub use pbr_material::*;
|
||||
|
@ -258,6 +260,7 @@ impl Plugin for PbrPlugin {
|
|||
FogPlugin,
|
||||
ExtractResourcePlugin::<DefaultOpaqueRendererMethod>::default(),
|
||||
ExtractComponentPlugin::<ShadowFilteringMethod>::default(),
|
||||
LightmapPlugin,
|
||||
))
|
||||
.configure_sets(
|
||||
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>,
|
||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||
images: Res<RenderAssets<Image>>,
|
||||
render_lightmaps: Res<RenderLightmaps>,
|
||||
mut views: Query<(
|
||||
&ExtractedView,
|
||||
&VisibleEntities,
|
||||
|
@ -613,6 +614,13 @@ pub fn queue_material_meshes<M: Material>(
|
|||
|
||||
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(
|
||||
&pipeline_cache,
|
||||
&material_pipeline,
|
||||
|
|
|
@ -462,6 +462,9 @@ pub struct StandardMaterial {
|
|||
/// Default is `16.0`.
|
||||
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`])
|
||||
pub opaque_render_method: OpaqueRendererMethod,
|
||||
|
||||
|
@ -513,6 +516,7 @@ impl Default for StandardMaterial {
|
|||
depth_map: None,
|
||||
parallax_depth_scale: 0.1,
|
||||
max_parallax_layer_count: 16.0,
|
||||
lightmap_exposure: 1.0,
|
||||
parallax_mapping_method: ParallaxMappingMethod::Occlusion,
|
||||
opaque_render_method: OpaqueRendererMethod::Auto,
|
||||
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,
|
||||
/// increase this value. However, this incurs a performance cost.
|
||||
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
|
||||
/// steps to use at most to find the depth value.
|
||||
pub max_relief_mapping_search_steps: u32,
|
||||
|
@ -720,6 +726,7 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
|
|||
alpha_cutoff,
|
||||
parallax_depth_scale: self.parallax_depth_scale,
|
||||
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(),
|
||||
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));
|
||||
}
|
||||
|
||||
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) {
|
||||
shader_defs.push("NORMAL_PREPASS".into());
|
||||
}
|
||||
|
@ -374,11 +379,11 @@ where
|
|||
.mesh_key
|
||||
.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());
|
||||
if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
|
||||
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) {
|
||||
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
|
||||
|
@ -681,6 +686,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||
render_mesh_instances: Res<RenderMeshInstances>,
|
||||
render_materials: Res<RenderMaterials<M>>,
|
||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||
render_lightmaps: Res<RenderLightmaps>,
|
||||
mut views: Query<
|
||||
(
|
||||
&ExtractedView,
|
||||
|
@ -793,6 +799,18 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||
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(
|
||||
&pipeline_cache,
|
||||
&prepass_pipeline,
|
||||
|
|
|
@ -62,6 +62,10 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
|
|||
out.uv = vertex.uv;
|
||||
#endif // VERTEX_UVS
|
||||
|
||||
#ifdef VERTEX_UVS_B
|
||||
out.uv_b = vertex.uv_b;
|
||||
#endif // VERTEX_UVS_B
|
||||
|
||||
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
||||
#ifdef SKINNED
|
||||
out.world_normal = skinning::skin_normals(model, vertex.normal);
|
||||
|
|
|
@ -10,20 +10,24 @@ struct Vertex {
|
|||
@location(1) uv: vec2<f32>,
|
||||
#endif
|
||||
|
||||
#ifdef VERTEX_UVS_B
|
||||
@location(2) uv_b: vec2<f32>,
|
||||
#endif
|
||||
|
||||
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
||||
@location(2) normal: vec3<f32>,
|
||||
@location(3) normal: vec3<f32>,
|
||||
#ifdef VERTEX_TANGENTS
|
||||
@location(3) tangent: vec4<f32>,
|
||||
@location(4) tangent: vec4<f32>,
|
||||
#endif
|
||||
#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
||||
|
||||
#ifdef SKINNED
|
||||
@location(4) joint_indices: vec4<u32>,
|
||||
@location(5) joint_weights: vec4<f32>,
|
||||
@location(5) joint_indices: vec4<u32>,
|
||||
@location(6) joint_weights: vec4<f32>,
|
||||
#endif
|
||||
|
||||
#ifdef VERTEX_COLORS
|
||||
@location(6) color: vec4<f32>,
|
||||
@location(7) color: vec4<f32>,
|
||||
#endif
|
||||
|
||||
#ifdef MORPH_TARGETS
|
||||
|
@ -40,27 +44,31 @@ struct VertexOutput {
|
|||
@location(0) uv: vec2<f32>,
|
||||
#endif
|
||||
|
||||
#ifdef VERTEX_UVS_B
|
||||
@location(1) uv_b: vec2<f32>,
|
||||
#endif
|
||||
|
||||
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
||||
@location(1) world_normal: vec3<f32>,
|
||||
@location(2) world_normal: vec3<f32>,
|
||||
#ifdef VERTEX_TANGENTS
|
||||
@location(2) world_tangent: vec4<f32>,
|
||||
@location(3) world_tangent: vec4<f32>,
|
||||
#endif
|
||||
#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
||||
|
||||
@location(3) world_position: vec4<f32>,
|
||||
@location(4) world_position: vec4<f32>,
|
||||
#ifdef MOTION_VECTOR_PREPASS
|
||||
@location(4) previous_world_position: vec4<f32>,
|
||||
@location(5) previous_world_position: vec4<f32>,
|
||||
#endif
|
||||
|
||||
#ifdef DEPTH_CLAMP_ORTHO
|
||||
@location(5) clip_position_unclamped: vec4<f32>,
|
||||
@location(6) clip_position_unclamped: vec4<f32>,
|
||||
#endif // DEPTH_CLAMP_ORTHO
|
||||
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
|
||||
@location(6) instance_index: u32,
|
||||
@location(7) instance_index: u32,
|
||||
#endif
|
||||
|
||||
#ifdef VERTEX_COLORS
|
||||
@location(7) color: vec4<f32>,
|
||||
@location(8) color: vec4<f32>,
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,9 @@ struct Vertex {
|
|||
#ifdef VERTEX_UVS
|
||||
@location(2) uv: vec2<f32>,
|
||||
#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
|
||||
@location(4) tangent: vec4<f32>,
|
||||
#endif
|
||||
|
@ -36,14 +38,17 @@ struct VertexOutput {
|
|||
#ifdef VERTEX_UVS
|
||||
@location(2) uv: vec2<f32>,
|
||||
#endif
|
||||
#ifdef VERTEX_UVS_B
|
||||
@location(3) uv_b: vec2<f32>,
|
||||
#endif
|
||||
#ifdef VERTEX_TANGENTS
|
||||
@location(3) world_tangent: vec4<f32>,
|
||||
@location(4) world_tangent: vec4<f32>,
|
||||
#endif
|
||||
#ifdef VERTEX_COLORS
|
||||
@location(4) color: vec4<f32>,
|
||||
@location(5) color: vec4<f32>,
|
||||
#endif
|
||||
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
|
||||
@location(5) @interpolate(flat) instance_index: u32,
|
||||
@location(6) @interpolate(flat) instance_index: u32,
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ use bevy_ecs::{
|
|||
query::{QueryItem, ROQueryItem},
|
||||
system::{lifetimeless::*, SystemParamItem, SystemState},
|
||||
};
|
||||
use bevy_math::{Affine3, Vec4};
|
||||
use bevy_math::{Affine3, Rect, UVec2, Vec4};
|
||||
use bevy_render::{
|
||||
batching::{
|
||||
batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData,
|
||||
|
@ -26,7 +26,7 @@ use bevy_render::{
|
|||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
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 thread_local::ThreadLocal;
|
||||
|
||||
|
@ -195,6 +195,16 @@ pub struct MeshUniform {
|
|||
// Affine 4x3 matrices transposed to 3x4
|
||||
pub 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:
|
||||
// [0].xyz, [1].x,
|
||||
// [1].yz, [2].xy
|
||||
|
@ -204,13 +214,14 @@ pub struct MeshUniform {
|
|||
pub flags: u32,
|
||||
}
|
||||
|
||||
impl From<&MeshTransforms> for MeshUniform {
|
||||
fn from(mesh_transforms: &MeshTransforms) -> Self {
|
||||
impl MeshUniform {
|
||||
fn new(mesh_transforms: &MeshTransforms, maybe_lightmap_uv_rect: Option<Rect>) -> Self {
|
||||
let (inverse_transpose_model_a, inverse_transpose_model_b) =
|
||||
mesh_transforms.transform.inverse_transpose_3x3();
|
||||
Self {
|
||||
transform: mesh_transforms.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_b,
|
||||
flags: mesh_transforms.flags,
|
||||
|
@ -447,24 +458,34 @@ impl MeshPipeline {
|
|||
}
|
||||
|
||||
impl GetBatchData for MeshPipeline {
|
||||
type Param = SRes<RenderMeshInstances>;
|
||||
type Param = (SRes<RenderMeshInstances>, SRes<RenderLightmaps>);
|
||||
type Data = Entity;
|
||||
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;
|
||||
|
||||
fn get_batch_data(
|
||||
mesh_instances: &SystemParamItem<Self::Param>,
|
||||
(mesh_instances, lightmaps): &SystemParamItem<Self::Param>,
|
||||
entity: &QueryItem<Self::Data>,
|
||||
) -> (Self::BufferData, Option<Self::CompareData>) {
|
||||
let mesh_instance = mesh_instances
|
||||
.get(entity)
|
||||
.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.material_bind_group_id,
|
||||
mesh_instance.mesh_asset_id,
|
||||
maybe_lightmap.map(|lightmap| lightmap.image),
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
@ -492,6 +513,7 @@ bitflags::bitflags! {
|
|||
const TEMPORAL_JITTER = 1 << 11;
|
||||
const MORPH_TARGETS = 1 << 12;
|
||||
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_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; //
|
||||
|
@ -609,21 +631,23 @@ pub fn setup_morph_and_skinning_defs(
|
|||
vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(offset + 1));
|
||||
};
|
||||
let is_morphed = key.intersects(MeshPipelineKey::MORPH_TARGETS);
|
||||
match (is_skinned(layout), is_morphed) {
|
||||
(true, false) => {
|
||||
let is_lightmapped = key.intersects(MeshPipelineKey::LIGHTMAPPED);
|
||||
match (is_skinned(layout), is_morphed, is_lightmapped) {
|
||||
(true, false, _) => {
|
||||
add_skin_data();
|
||||
mesh_layouts.skinned.clone()
|
||||
}
|
||||
(true, true) => {
|
||||
(true, true, _) => {
|
||||
add_skin_data();
|
||||
shader_defs.push("MORPH_TARGETS".into());
|
||||
mesh_layouts.morphed_skinned.clone()
|
||||
}
|
||||
(false, true) => {
|
||||
(false, true, _) => {
|
||||
shader_defs.push("MORPH_TARGETS".into());
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -810,6 +834,10 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
|||
shader_defs.push("ENVIRONMENT_MAP".into());
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::LIGHTMAPPED) {
|
||||
shader_defs.push("LIGHTMAP".into());
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::TEMPORAL_JITTER) {
|
||||
shader_defs.push("TEMPORAL_JITTER".into());
|
||||
}
|
||||
|
@ -922,36 +950,44 @@ pub struct MeshBindGroups {
|
|||
model_only: Option<BindGroup>,
|
||||
skinned: Option<BindGroup>,
|
||||
morph_targets: HashMap<AssetId<Mesh>, BindGroup>,
|
||||
lightmaps: HashMap<AssetId<Image>, BindGroup>,
|
||||
}
|
||||
impl MeshBindGroups {
|
||||
pub fn reset(&mut self) {
|
||||
self.model_only = None;
|
||||
self.skinned = None;
|
||||
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(
|
||||
&self,
|
||||
asset_id: AssetId<Mesh>,
|
||||
lightmap: Option<AssetId<Image>>,
|
||||
is_skinned: bool,
|
||||
morph: bool,
|
||||
) -> Option<&BindGroup> {
|
||||
match (is_skinned, morph) {
|
||||
(_, true) => self.morph_targets.get(&asset_id),
|
||||
(true, false) => self.skinned.as_ref(),
|
||||
(false, false) => self.model_only.as_ref(),
|
||||
match (is_skinned, morph, lightmap) {
|
||||
(_, true, _) => self.morph_targets.get(&asset_id),
|
||||
(true, false, _) => self.skinned.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(
|
||||
meshes: Res<RenderAssets<Mesh>>,
|
||||
images: Res<RenderAssets<Image>>,
|
||||
mut groups: ResMut<MeshBindGroups>,
|
||||
mesh_pipeline: Res<MeshPipeline>,
|
||||
render_device: Res<RenderDevice>,
|
||||
mesh_uniforms: Res<GpuArrayBuffer<MeshUniform>>,
|
||||
skins_uniform: Res<SkinUniform>,
|
||||
weights_uniform: Res<MorphUniform>,
|
||||
render_lightmaps: Res<RenderLightmaps>,
|
||||
) {
|
||||
groups.reset();
|
||||
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>;
|
||||
|
@ -1018,6 +1063,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
|
|||
SRes<RenderMeshInstances>,
|
||||
SRes<SkinIndices>,
|
||||
SRes<MorphIndices>,
|
||||
SRes<RenderLightmaps>,
|
||||
);
|
||||
type ViewData = ();
|
||||
type ItemData = ();
|
||||
|
@ -1027,7 +1073,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
|
|||
item: &P,
|
||||
_view: (),
|
||||
_item_query: (),
|
||||
(bind_groups, mesh_instances, skin_indices, morph_indices): SystemParamItem<
|
||||
(bind_groups, mesh_instances, skin_indices, morph_indices, lightmaps): SystemParamItem<
|
||||
'w,
|
||||
'_,
|
||||
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_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!(
|
||||
"The MeshBindGroups resource wasn't set in the render phase. \
|
||||
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;
|
||||
#endif
|
||||
|
||||
#ifdef VERTEX_UVS_B
|
||||
out.uv_b = vertex.uv_b;
|
||||
#endif
|
||||
|
||||
#ifdef VERTEX_TANGENTS
|
||||
out.world_tangent = mesh_functions::mesh_tangent_local_to_world(
|
||||
model,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
//! Bind group layout related definitions for the mesh pipeline.
|
||||
|
||||
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;
|
||||
|
||||
|
@ -17,9 +19,9 @@ mod layout_entry {
|
|||
use crate::MeshUniform;
|
||||
use bevy_render::{
|
||||
render_resource::{
|
||||
binding_types::{texture_3d, uniform_buffer_sized},
|
||||
BindGroupLayoutEntryBuilder, BufferSize, GpuArrayBuffer, ShaderStages,
|
||||
TextureSampleType,
|
||||
binding_types::{sampler, texture_2d, texture_3d, uniform_buffer_sized},
|
||||
BindGroupLayoutEntryBuilder, BufferSize, GpuArrayBuffer, SamplerBindingType,
|
||||
ShaderStages, TextureSampleType,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
};
|
||||
|
@ -37,6 +39,12 @@ mod layout_entry {
|
|||
pub(super) fn targets() -> BindGroupLayoutEntryBuilder {
|
||||
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`]
|
||||
|
@ -44,7 +52,7 @@ mod layout_entry {
|
|||
mod entry {
|
||||
use super::{JOINT_BUFFER_SIZE, MORPH_BUFFER_SIZE};
|
||||
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 {
|
||||
|
@ -72,6 +80,18 @@ mod entry {
|
|||
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`).
|
||||
|
@ -80,6 +100,9 @@ pub struct MeshLayouts {
|
|||
/// The mesh model uniform (transform) and nothing else.
|
||||
pub model_only: BindGroupLayout,
|
||||
|
||||
/// Includes the lightmap texture and uniform.
|
||||
pub lightmapped: BindGroupLayout,
|
||||
|
||||
/// Also includes the uniform for skinning
|
||||
pub skinned: BindGroupLayout,
|
||||
|
||||
|
@ -102,6 +125,7 @@ impl MeshLayouts {
|
|||
pub fn new(render_device: &RenderDevice) -> Self {
|
||||
MeshLayouts {
|
||||
model_only: Self::model_only_layout(render_device),
|
||||
lightmapped: Self::lightmapped_layout(render_device),
|
||||
skinned: Self::skinned_layout(render_device),
|
||||
morphed: Self::morphed_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 ----------
|
||||
|
||||
|
@ -168,6 +205,22 @@ impl MeshLayouts {
|
|||
&[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(
|
||||
&self,
|
||||
render_device: &RenderDevice,
|
||||
|
|
|
@ -5,6 +5,7 @@ struct Mesh {
|
|||
// Use bevy_render::maths::affine_to_square to unpack
|
||||
model: mat3x4<f32>,
|
||||
previous_model: mat3x4<f32>,
|
||||
lightmap_uv_rect: vec2<u32>,
|
||||
// 3x3 matrix packed in mat2x4 and f32 as:
|
||||
// [0].xyz, [1].x,
|
||||
// [1].yz, [2].xy
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
mesh_bindings::mesh,
|
||||
mesh_view_bindings::view,
|
||||
parallax_mapping::parallaxed_uv,
|
||||
lightmap::lightmap,
|
||||
}
|
||||
|
||||
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
|
||||
|
@ -191,6 +192,13 @@ fn pbr_input_from_standard_material(
|
|||
view.mip_bias,
|
||||
);
|
||||
#endif
|
||||
|
||||
#ifdef LIGHTMAP
|
||||
pbr_input.lightmap_light = lightmap(
|
||||
in.uv_b,
|
||||
pbr_bindings::material.lightmap_exposure,
|
||||
in.instance_index);
|
||||
#endif
|
||||
}
|
||||
|
||||
return pbr_input;
|
||||
|
|
|
@ -358,6 +358,10 @@ fn apply_pbr_lighting(
|
|||
let specular_transmitted_environment_light = vec3<f32>(0.0);
|
||||
#endif
|
||||
|
||||
#ifdef LIGHTMAP
|
||||
indirect_light += in.lightmap_light * diffuse_color;
|
||||
#endif
|
||||
|
||||
let emissive_light = emissive.rgb * output_color.a;
|
||||
|
||||
if specular_transmission > 0.0 {
|
||||
|
|
|
@ -17,6 +17,7 @@ struct StandardMaterial {
|
|||
alpha_cutoff: f32,
|
||||
parallax_depth_scale: f32,
|
||||
max_parallax_layer_count: f32,
|
||||
lightmap_exposure: f32,
|
||||
max_relief_mapping_search_steps: u32,
|
||||
/// ID for specifying which deferred lighting pass should be used for rendering this material, if any.
|
||||
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
|
||||
// view world position
|
||||
V: vec3<f32>,
|
||||
lightmap_light: vec3<f32>,
|
||||
is_orthographic: bool,
|
||||
flags: u32,
|
||||
};
|
||||
|
@ -110,6 +112,8 @@ fn pbr_input_new() -> PbrInput {
|
|||
pbr_input.N = vec3<f32>(0.0, 0.0, 1.0);
|
||||
pbr_input.V = vec3<f32>(1.0, 0.0, 0.0);
|
||||
|
||||
pbr_input.lightmap_light = vec3<f32>(0.0);
|
||||
|
||||
pbr_input.flags = 0u;
|
||||
|
||||
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
|
||||
[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
|
||||
[Lightmaps](../examples/3d/lightmaps.rs) | Rendering a scene with baked lightmaps
|
||||
[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
|
||||
[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