Skip to content

Commit

Permalink
Replace Ambient Lights with Environment Map Lights (#17482)
Browse files Browse the repository at this point in the history
# Objective

Transparently uses simple `EnvironmentMapLight`s to mimic
`AmbientLight`s. Implements the first part of #17468, but I can
implement hemispherical lights in this PR too if needed.

## Solution

- A function `EnvironmentMapLight::solid_color(&mut Assets<Image>,
Color)` is provided to make an environment light with a solid color.
- A new system is added to `SimulationLightSystems` that maps
`AmbientLight`s on views or the world to a corresponding
`EnvironmentMapLight`.

I have never worked with (or on) Bevy before, so nitpicky comments on
how I did things are appreciated :).

## Testing

Testing was done on a modified version of the `3d/lighting` example,
where I removed all lights except the ambient light. I have not included
the example, but can if required.

## Migration
`bevy_pbr::AmbientLight` has been deprecated, so all usages of it should
be replaced by a `bevy_pbr::EnvironmentMapLight` created with
`EnvironmentMapLight::solid_color` placed on the camera. There is no
alternative to ambient lights as resources.
  • Loading branch information
SparkyPotato authored Mar 4, 2025
1 parent 91ce99b commit 17e300b
Show file tree
Hide file tree
Showing 35 changed files with 348 additions and 212 deletions.
22 changes: 14 additions & 8 deletions crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,16 @@ pub use volumetric_fog::{FogVolume, VolumetricFog, VolumetricFogPlugin, Volumetr
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[expect(
deprecated,
reason = "AmbientLight has been replaced by EnvironmentMapLight"
)]
#[doc(hidden)]
pub use crate::light::AmbientLight;
#[doc(hidden)]
pub use crate::{
fog::{DistanceFog, FogFalloff},
light::{light_consts, AmbientLight, DirectionalLight, PointLight, SpotLight},
light::{light_consts, DirectionalLight, PointLight, SpotLight},
light_probe::{environment_map::EnvironmentMapLight, LightProbe},
material::{Material, MaterialPlugin},
mesh_material::MeshMaterial3d,
Expand Down Expand Up @@ -165,7 +171,6 @@ pub const PBR_PREPASS_SHADER_HANDLE: Handle<Shader> =
weak_handle!("9afeaeab-7c45-43ce-b322-4b97799eaeb9");
pub const PBR_FUNCTIONS_HANDLE: Handle<Shader> =
weak_handle!("815b8618-f557-4a96-91a5-a2fb7e249fb0");
pub const PBR_AMBIENT_HANDLE: Handle<Shader> = weak_handle!("4a90b95b-112a-4a10-9145-7590d6f14260");
pub const PARALLAX_MAPPING_SHADER_HANDLE: Handle<Shader> =
weak_handle!("6cf57d9f-222a-429a-bba4-55ba9586e1d4");
pub const VIEW_TRANSFORMATIONS_SHADER_HANDLE: Handle<Shader> =
Expand Down Expand Up @@ -280,12 +285,6 @@ impl Plugin for PbrPlugin {
"render/rgb9e5.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_AMBIENT_HANDLE,
"render/pbr_ambient.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_FRAGMENT_HANDLE,
Expand Down Expand Up @@ -325,6 +324,10 @@ impl Plugin for PbrPlugin {
Shader::from_wgsl
);

#[expect(
deprecated,
reason = "AmbientLight has been replaced by EnvironmentMapLight"
)]
app.register_asset_reflect::<StandardMaterial>()
.register_type::<AmbientLight>()
.register_type::<CascadeShadowConfig>()
Expand Down Expand Up @@ -401,6 +404,9 @@ impl Plugin for PbrPlugin {
.add_systems(
PostUpdate,
(
map_ambient_lights
.in_set(SimulationLightSystems::MapAmbientLights)
.after(CameraUpdateSystem),
add_clusters
.in_set(SimulationLightSystems::AddClusters)
.after(CameraUpdateSystem),
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_pbr/src/light/ambient_light.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#![deprecated(
since = "0.16.0",
note = "Use `EnvironmentMapLight::solid_color` instead"
)]

use super::*;

/// An ambient light, which lights the entire scene equally.
Expand Down
59 changes: 58 additions & 1 deletion crates/bevy_pbr/src/light/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ use bevy_render::{
use bevy_transform::components::{GlobalTransform, Transform};
use bevy_utils::Parallel;

use crate::*;
use crate::{prelude::EnvironmentMapLight, *};

mod ambient_light;
#[expect(
deprecated,
reason = "AmbientLight has been replaced by EnvironmentMapLight"
)]
pub use ambient_light::AmbientLight;

mod point_light;
Expand Down Expand Up @@ -509,6 +513,7 @@ pub struct LightVisibilityClass;
/// System sets used to run light-related systems.
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum SimulationLightSystems {
MapAmbientLights,
AddClusters,
AssignLightsToClusters,
/// System order ambiguities between systems in this set are ignored:
Expand All @@ -522,6 +527,58 @@ pub enum SimulationLightSystems {
CheckLightVisibility,
}

#[derive(Component)]
pub struct EnvironmentMapLightFromAmbientLight;

#[expect(
deprecated,
reason = "AmbientLight has been replaced by EnvironmentMapLight"
)]
pub fn map_ambient_lights(
mut commands: Commands,
mut image_assets: ResMut<Assets<Image>>,
ambient_light: Res<AmbientLight>,
new_views: Query<
(Entity, Option<Ref<AmbientLight>>),
(
With<Camera>,
Without<EnvironmentMapLight>,
Without<EnvironmentMapLightFromAmbientLight>,
),
>,
mut managed_views: Query<
(&mut EnvironmentMapLight, Option<Ref<AmbientLight>>),
With<EnvironmentMapLightFromAmbientLight>,
>,
) {
let ambient_light = ambient_light.into();
for (entity, ambient_override) in new_views.iter() {
let ambient = ambient_override.as_ref().unwrap_or(&ambient_light);
let ambient_required = ambient.brightness > 0.0 && ambient.color != Color::BLACK;
if ambient_required && ambient.is_changed() {
commands
.entity(entity)
.insert(EnvironmentMapLight {
intensity: ambient.brightness,
affects_lightmapped_mesh_diffuse: ambient.affects_lightmapped_meshes,
..EnvironmentMapLight::solid_color(image_assets.as_mut(), ambient.color)
})
.insert(EnvironmentMapLightFromAmbientLight);
}
}
for (mut env_map, ambient_override) in managed_views.iter_mut() {
let ambient = ambient_override.as_ref().unwrap_or(&ambient_light);
let ambient_required = ambient.brightness > 0.0 && ambient.color != Color::BLACK;
if ambient_required && ambient.is_changed() {
*env_map = EnvironmentMapLight {
intensity: ambient.brightness,
affects_lightmapped_mesh_diffuse: ambient.affects_lightmapped_meshes,
..EnvironmentMapLight::solid_color(image_assets.as_mut(), ambient.color)
};
}
}
}

pub fn update_directional_light_frusta(
mut views: Query<
(
Expand Down
85 changes: 82 additions & 3 deletions crates/bevy_pbr/src/light_probe/environment_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
//!
//! [several pre-filtered environment maps]: https://github.com/KhronosGroup/glTF-Sample-Environments
use bevy_asset::{weak_handle, AssetId, Handle};
use bevy_asset::{weak_handle, AssetId, Assets, Handle, RenderAssetUsages};
use bevy_color::{Color, ColorToPacked, Srgba};
use bevy_ecs::{
component::Component, query::QueryItem, reflect::ReflectComponent, system::lifetimeless::Read,
};
Expand All @@ -56,8 +57,9 @@ use bevy_render::{
render_asset::RenderAssets,
render_resource::{
binding_types::{self, uniform_buffer},
BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, Shader, ShaderStages,
TextureSampleType, TextureView,
BindGroupLayoutEntryBuilder, Extent3d, Sampler, SamplerBindingType, Shader, ShaderStages,
TextureDimension, TextureFormat, TextureSampleType, TextureView, TextureViewDescriptor,
TextureViewDimension,
},
renderer::{RenderAdapter, RenderDevice},
texture::{FallbackImage, GpuImage},
Expand Down Expand Up @@ -114,6 +116,83 @@ pub struct EnvironmentMapLight {
pub affects_lightmapped_mesh_diffuse: bool,
}

impl EnvironmentMapLight {
/// An environment map with a uniform color, useful for uniform ambient lighting.
pub fn solid_color(assets: &mut Assets<Image>, color: Color) -> Self {
let color: Srgba = color.into();
let image = Image {
texture_view_descriptor: Some(TextureViewDescriptor {
dimension: Some(TextureViewDimension::Cube),
..Default::default()
}),
..Image::new_fill(
Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 6,
},
TextureDimension::D2,
&color.to_u8_array(),
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::RENDER_WORLD,
)
};
let handle = assets.add(image);

Self {
diffuse_map: handle.clone(),
specular_map: handle,
..Default::default()
}
}

/// An environment map with a hemispherical gradient, fading between the sky and ground colors
/// at the horizon. Useful as a very simple 'sky'.
pub fn hemispherical_gradient(
assets: &mut Assets<Image>,
top_color: Color,
bottom_color: Color,
) -> Self {
let top_color: Srgba = top_color.into();
let bottom_color: Srgba = bottom_color.into();
let mid_color = (top_color + bottom_color) / 2.0;
let image = Image {
texture_view_descriptor: Some(TextureViewDescriptor {
dimension: Some(TextureViewDimension::Cube),
..Default::default()
}),
..Image::new(
Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 6,
},
TextureDimension::D2,
[
mid_color,
mid_color,
top_color,
bottom_color,
mid_color,
mid_color,
]
.into_iter()
.flat_map(Srgba::to_u8_array)
.collect(),
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::RENDER_WORLD,
)
};
let handle = assets.add(image);

Self {
diffuse_map: handle.clone(),
specular_map: handle,
..Default::default()
}
}
}

impl Default for EnvironmentMapLight {
fn default() -> Self {
EnvironmentMapLight {
Expand Down
26 changes: 5 additions & 21 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ bitflags::bitflags! {
#[derive(Copy, Clone, Debug, ShaderType)]
pub struct GpuLights {
directional_lights: [GpuDirectionalLight; MAX_DIRECTIONAL_LIGHTS],
ambient_color: Vec4,
// xyz are x/y/z cluster dimensions and w is the number of clusters
cluster_dimensions: UVec4,
// xy are vec2<f32>(cluster_dimensions.xy) / vec2<f32>(view.width, view.height)
Expand All @@ -149,7 +148,6 @@ pub struct GpuLights {
n_directional_lights: u32,
// offset from spot light's light index to spot light's shadow map index
spot_light_shadowmap_offset: i32,
ambient_light_affects_lightmapped_meshes: u32,
}

// NOTE: When running bevy on Adreno GPU chipsets in WebGL, any value above 1 will result in a crash
Expand Down Expand Up @@ -729,11 +727,9 @@ pub fn prepare_lights(
&ExtractedClusterConfig,
Option<&RenderLayers>,
Has<NoIndirectDrawing>,
Option<&AmbientLight>,
),
With<Camera3d>,
>,
ambient_light: Res<AmbientLight>,
point_light_shadow_map: Res<PointLightShadowMap>,
directional_light_shadow_map: Res<DirectionalLightShadowMap>,
mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
Expand Down Expand Up @@ -1142,18 +1138,11 @@ pub fn prepare_lights(
let mut live_views = EntityHashSet::with_capacity(views_count);

// set up light data for each view
for (
entity,
camera_main_entity,
extracted_view,
clusters,
maybe_layers,
no_indirect_drawing,
maybe_ambient_override,
) in sorted_cameras
.0
.iter()
.filter_map(|sorted_camera| views.get(sorted_camera.entity).ok())
for (entity, camera_main_entity, extracted_view, clusters, maybe_layers, no_indirect_drawing) in
sorted_cameras
.0
.iter()
.filter_map(|sorted_camera| views.get(sorted_camera.entity).ok())
{
live_views.insert(entity);

Expand All @@ -1175,11 +1164,8 @@ pub fn prepare_lights(
);

let n_clusters = clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z;
let ambient_light = maybe_ambient_override.unwrap_or(&ambient_light);
let mut gpu_lights = GpuLights {
directional_lights: gpu_directional_lights,
ambient_color: Vec4::from_slice(&LinearRgba::from(ambient_light.color).to_f32_array())
* ambient_light.brightness,
cluster_factors: Vec4::new(
clusters.dimensions.x as f32 / extracted_view.viewport.z as f32,
clusters.dimensions.y as f32 / extracted_view.viewport.w as f32,
Expand All @@ -1194,8 +1180,6 @@ pub fn prepare_lights(
// index to shadow map index, we need to subtract point light count and add directional shadowmap count.
spot_light_shadowmap_offset: num_directional_cascades_enabled as i32
- point_light_count as i32,
ambient_light_affects_lightmapped_meshes: ambient_light.affects_lightmapped_meshes
as u32,
};

// TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query
Expand Down
1 change: 0 additions & 1 deletion crates/bevy_pbr/src/render/mesh_view_types.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ const DIRECTIONAL_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE_BIT: u32 = 4u;
struct Lights {
// NOTE: this array size must be kept in sync with the constants defined in bevy_pbr/src/render/light.rs
directional_lights: array<DirectionalLight, #{MAX_DIRECTIONAL_LIGHTS}u>,
ambient_color: vec4<f32>,
// x/y/z dimensions and n_clusters in w
cluster_dimensions: vec4<u32>,
// xy are vec2<f32>(cluster_dimensions.xy) / vec2<f32>(view.width, view.height)
Expand Down
29 changes: 0 additions & 29 deletions crates/bevy_pbr/src/render/pbr_ambient.wgsl

This file was deleted.

15 changes: 0 additions & 15 deletions crates/bevy_pbr/src/render/pbr_functions.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -561,18 +561,6 @@ fn apply_pbr_lighting(
#endif
}

#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION
// NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
// world position, inverted normal and view vectors, and the following simplified
// values for a fully diffuse transmitted light contribution approximation:
//
// perceptual_roughness = 1.0;
// NdotV = 1.0;
// F0 = vec3<f32>(0.0)
// diffuse_occlusion = vec3<f32>(1.0)
transmitted_light += ambient::ambient_light(diffuse_transmissive_lobe_world_position, -in.N, -in.V, 1.0, diffuse_transmissive_color, vec3<f32>(0.0), 1.0, vec3<f32>(1.0));
#endif

// Diffuse indirect lighting can come from a variety of sources. The
// priority goes like this:
//
Expand Down Expand Up @@ -644,9 +632,6 @@ fn apply_pbr_lighting(

#endif // ENVIRONMENT_MAP

// Ambient light (indirect)
indirect_light += ambient::ambient_light(in.world_position, in.N, in.V, NdotV, diffuse_color, F0, perceptual_roughness, diffuse_occlusion);

// we'll use the specular component of the transmitted environment
// light in the call to `specular_transmissive_light()` below
var specular_transmitted_environment_light = vec3<f32>(0.0);
Expand Down
Loading

0 comments on commit 17e300b

Please sign in to comment.