Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: "`EnvironmentMapUniform` is removed"
pull_requests: [24095]
---

`EnvironmentMapUniform` has been removed. It previously stored the rotation transformation matrix of view environment maps. Now the rotation is stored as a quaternion in `LightProbesUniform::view_rotation`.
2 changes: 2 additions & 0 deletions crates/bevy_light/src/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ pub struct EnvironmentMapLight {
/// World space rotation applied to the environment light cubemaps.
/// This is useful for users who require a different axis, such as the Z-axis, to serve
/// as the vertical axis.
///
/// Note: This only has an effect if attached to a view.
pub rotation: Quat,

/// Whether the light from this environment map contributes diffuse lighting
Expand Down
3 changes: 0 additions & 3 deletions crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,6 @@ fn shader_ref(path: PathBuf) -> ShaderRef {
ShaderRef::Path(AssetPath::from_path_buf(path).with_source("embedded"))
}

pub const TONEMAPPING_LUT_TEXTURE_BINDING_INDEX: u32 = 19;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this change?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved it because changing the mesh view binding but forgetting to changing it was a footgun - it took me a while to find the cause.

pub const TONEMAPPING_LUT_SAMPLER_BINDING_INDEX: u32 = 20;

/// Sets up the entire PBR infrastructure of bevy.
pub struct PbrPlugin {
/// Controls if the prepass is enabled for the [`StandardMaterial`].
Expand Down
17 changes: 10 additions & 7 deletions crates/bevy_pbr/src/light_probe/environment_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,12 @@ use bevy_ecs::{
};
use bevy_image::Image;
use bevy_light::{EnvironmentMapLight, ParallaxCorrection};
use bevy_math::{Affine3A, Vec3};
use bevy_math::{Affine3A, Quat, Vec3};
use bevy_render::{
extract_instances::ExtractInstance,
render_asset::RenderAssets,
render_resource::{
binding_types::{self, uniform_buffer},
BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, ShaderStages, TextureSampleType,
binding_types, BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, TextureSampleType,
TextureView,
},
renderer::{RenderAdapter, RenderDevice},
Expand All @@ -67,8 +66,8 @@ use bevy_render::{
use core::{num::NonZero, ops::Deref};

use crate::{
add_cubemap_texture_view, binding_arrays_are_usable, EnvironmentMapUniform,
RenderLightProbeFlags, MAX_VIEW_LIGHT_PROBES,
add_cubemap_texture_view, binding_arrays_are_usable, RenderLightProbeFlags,
MAX_VIEW_LIGHT_PROBES,
};

use super::{LightProbeComponent, RenderViewLightProbes};
Expand Down Expand Up @@ -136,6 +135,8 @@ pub struct EnvironmentMapViewLightProbeInfo {
/// Whether this lightmap affects the diffuse lighting of lightmapped
/// meshes.
pub(crate) affects_lightmapped_mesh_diffuse: bool,
/// World space rotation applied to the environment light cubemaps.
pub(crate) rotation: Quat,
}

impl ExtractInstance for EnvironmentMapIds {
Expand All @@ -156,7 +157,7 @@ impl ExtractInstance for EnvironmentMapIds {
pub(crate) fn get_bind_group_layout_entries(
render_device: &RenderDevice,
render_adapter: &RenderAdapter,
) -> [BindGroupLayoutEntryBuilder; 4] {
) -> [BindGroupLayoutEntryBuilder; 3] {
let mut texture_cube_binding =
binding_types::texture_cube(TextureSampleType::Float { filterable: true });
if binding_arrays_are_usable(render_device, render_adapter) {
Expand All @@ -168,7 +169,6 @@ pub(crate) fn get_bind_group_layout_entries(
texture_cube_binding,
texture_cube_binding,
binding_types::sampler(SamplerBindingType::Filtering),
uniform_buffer::<EnvironmentMapUniform>(true).visibility(ShaderStages::FRAGMENT),
]
}

Expand Down Expand Up @@ -295,6 +295,7 @@ impl LightProbeComponent for EnvironmentMapLight {
specular_map: specular_map_handle,
intensity,
affects_lightmapped_mesh_diffuse,
rotation,
..
}) = view_component
&& let (Some(_), Some(specular_map)) = (
Expand All @@ -314,6 +315,7 @@ impl LightProbeComponent for EnvironmentMapLight {
- 1,
intensity: *intensity,
affects_lightmapped_mesh_diffuse: *affects_lightmapped_mesh_diffuse,
rotation: *rotation,
});
};

Expand Down Expand Up @@ -344,6 +346,7 @@ impl Default for EnvironmentMapViewLightProbeInfo {
smallest_specular_mip_level: 0,
intensity: 1.0,
affects_lightmapped_mesh_diffuse: true,
rotation: Quat::IDENTITY,
}
}
}
35 changes: 24 additions & 11 deletions crates/bevy_pbr/src/light_probe/environment_map.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#import bevy_pbr::light_probe::{light_probe_iterator_new, light_probe_iterator_next}
#import bevy_pbr::mesh_view_bindings as bindings
#import bevy_pbr::mesh_view_bindings::light_probes
#import bevy_pbr::mesh_view_bindings::environment_map_uniform
#import bevy_pbr::mesh_view_types::{
LIGHT_PROBE_FLAG_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE, LIGHT_PROBE_FLAG_PARALLAX_CORRECT
}
Expand Down Expand Up @@ -45,7 +44,8 @@ fn compute_cubemap_sample_dir(
world_ray_direction: vec3<f32>,
light_from_world: mat4x4<f32>,
parallax_correction_bounds: vec3<f32>,
parallax_correct: bool
parallax_correct: bool,
view_rotation: vec4<f32>,
) -> vec3<f32> {
var sample_dir: vec3<f32>;

Expand Down Expand Up @@ -85,6 +85,10 @@ fn compute_cubemap_sample_dir(
sample_dir = (light_from_world * vec4(world_ray_direction, 0.0)).xyz;
}

// Rotating the world space ray direction by the environment map light view rotation,
// it is equivalent to rotating the environment cubemap itself.
sample_dir = quat_rotate(view_rotation, sample_dir);

// Cubemaps are left-handed, so we negate the Z coordinate.
sample_dir.z = -sample_dir.z;
return sample_dir;
Expand Down Expand Up @@ -124,6 +128,7 @@ fn compute_radiances(

while (true) {
var query_result = light_probe_iterator_next(&iterator);
var view_rotation = vec4<f32>(0.0, 0.0, 0.0, 1.0);

// If we reached the end of the light probe list, and we didn't find
// enough reflection probes to reach a weight of 1.0, use the view
Expand All @@ -132,6 +137,7 @@ fn compute_radiances(
if (query_result.texture_index < 0 && total_weight < 0.9999) {
query_result.texture_index = light_probes.view_cubemap_index;
query_result.intensity = light_probes.intensity_for_view;
view_rotation = light_probes.view_rotation;
query_result.light_from_world = mat4x4(
vec4(1.0, 0.0, 0.0, 0.0),
vec4(0.0, 1.0, 0.0, 0.0),
Expand Down Expand Up @@ -172,7 +178,8 @@ fn compute_radiances(
N,
query_result.light_from_world,
query_result.parallax_correction_bounds,
parallax_correct
parallax_correct,
view_rotation,
);
radiances.irradiance = textureSampleLevel(
bindings::diffuse_environment_maps[query_result.texture_index],
Expand All @@ -187,7 +194,8 @@ fn compute_radiances(
radiance_sample_dir,
query_result.light_from_world,
query_result.parallax_correction_bounds,
parallax_correct
parallax_correct,
view_rotation,
);
radiances.radiance +=
textureSampleLevel(
Expand Down Expand Up @@ -247,9 +255,9 @@ fn compute_radiances(

if (enable_diffuse) {
var irradiance_sample_dir = N;
// Rotating the world space ray direction by the environment light map transform matrix, it is
// equivalent to rotating the diffuse environment cubemap itself.
irradiance_sample_dir = (environment_map_uniform.transform * vec4(irradiance_sample_dir, 1.0)).xyz;
// Rotating the world space ray direction by the environment map light view rotation,
// it is equivalent to rotating the environment cubemap itself.
irradiance_sample_dir = quat_rotate(light_probes.view_rotation, irradiance_sample_dir);
// Cube maps are left-handed so we negate the z coordinate.
irradiance_sample_dir.z = -irradiance_sample_dir.z;
radiances.irradiance = textureSampleLevel(
Expand All @@ -260,9 +268,9 @@ fn compute_radiances(
}

var radiance_sample_dir = radiance_sample_direction(N, R, roughness);
// Rotating the world space ray direction by the environment light map transform matrix, it is
// equivalent to rotating the specular environment cubemap itself.
radiance_sample_dir = (environment_map_uniform.transform * vec4(radiance_sample_dir, 1.0)).xyz;
// Rotating the world space ray direction by the environment map light view rotation,
// it is equivalent to rotating the environment cubemap itself.
radiance_sample_dir = quat_rotate(light_probes.view_rotation, radiance_sample_dir);
// Cube maps are left-handed so we negate the z coordinate.
radiance_sample_dir.z = -radiance_sample_dir.z;
radiances.radiance = textureSampleLevel(
Expand Down Expand Up @@ -367,7 +375,7 @@ fn environment_map_light(
let F0_surface = mix(F0_dielectric, F0_metallic, metallic);
let specular_occlusion = saturate(dot(F0_surface, vec3(50.0 * 0.33)));

// Compute per-material (dielectric and metallic separately) then mix the results.
// Compute per-material (dielectric and metallic separately) then mix the results.
// We can't use F0 directly as the multiscattering term is nonlinear.
let Ems = 1.0 - (F_ab.x + F_ab.y);

Expand Down Expand Up @@ -405,3 +413,8 @@ fn radiance_sample_direction(N: vec3<f32>, R: vec3<f32>, roughness: f32) -> vec3
let lerp_factor = smoothness * (sqrt(smoothness) + roughness);
return mix(N, R, lerp_factor);
}

fn quat_rotate(q: vec4<f32>, dir: vec3<f32>) -> vec3<f32> {
let t = 2.0 * cross(q.xyz, dir);
return dir + q.w * t + cross(q.xyz, t);
}
86 changes: 11 additions & 75 deletions crates/bevy_pbr/src/light_probe/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use bevy_image::Image;
use bevy_light::{
cluster::ClusterVisibilityClass, EnvironmentMapLight, IrradianceVolume, LightProbe,
};
use bevy_math::{Affine3A, FloatOrd, Mat4, Vec3, Vec4};
use bevy_math::{Affine3A, FloatOrd, Mat4, Quat, Vec3, Vec4};
use bevy_platform::collections::HashMap;
use bevy_render::{
extract_instances::ExtractInstancesPlugin,
Expand All @@ -30,7 +30,7 @@ use bevy_render::{
Extract, ExtractSchedule, GpuResourceAppExt, Render, RenderApp, RenderSystems,
};
use bevy_shader::load_shader_library;
use bevy_transform::{components::Transform, prelude::GlobalTransform};
use bevy_transform::prelude::GlobalTransform;
use bitflags::bitflags;
use tracing::error;

Expand Down Expand Up @@ -143,6 +143,9 @@ pub struct LightProbesUniform {
/// associated with the view.
smallest_specular_mip_level_for_view: u32,

/// World space rotation applied to environment map cubemaps associated with the view itself.
view_rotation: Vec4,

/// The intensity of the environment cubemap associated with the view.
///
/// See the comment in [`EnvironmentMapLight`] for details.
Expand Down Expand Up @@ -367,31 +370,6 @@ pub trait LightProbeComponent: Send + Sync + Component + Sized {
}
}

/// The uniform struct extracted from [`EnvironmentMapLight`].
/// Will be available for use in the Environment Map shader.
#[derive(Component, ShaderType, Clone)]
pub struct EnvironmentMapUniform {
/// The world space transformation matrix of the sample ray for environment cubemaps.
transform: Mat4,
}

impl Default for EnvironmentMapUniform {
fn default() -> Self {
EnvironmentMapUniform {
transform: Mat4::IDENTITY,
}
}
}

/// A GPU buffer that stores the environment map settings for each view.
#[derive(Resource, Default, Deref, DerefMut)]
pub struct EnvironmentMapUniformBuffer(pub DynamicUniformBuffer<EnvironmentMapUniform>);

/// A component that stores the offset within the
/// [`EnvironmentMapUniformBuffer`] for each view.
#[derive(Component, Default, Deref, DerefMut)]
pub struct ViewEnvironmentMapUniformOffset(u32);

impl Plugin for LightProbePlugin {
fn build(&self, app: &mut App) {
load_shader_library!(app, "light_probe.wgsl");
Expand All @@ -409,8 +387,6 @@ impl Plugin for LightProbePlugin {

render_app
.init_gpu_resource::<LightProbesBuffer>()
.init_gpu_resource::<EnvironmentMapUniformBuffer>()
.add_systems(ExtractSchedule, gather_environment_map_uniform)
.add_systems(
ExtractSchedule,
gather_light_probes::<EnvironmentMapLight>
Expand All @@ -425,33 +401,11 @@ impl Plugin for LightProbePlugin {
)
.add_systems(
Render,
(upload_light_probes, prepare_environment_uniform_buffer)
.in_set(RenderSystems::PrepareResources),
upload_light_probes.in_set(RenderSystems::PrepareResources),
);
}
}

/// Extracts [`EnvironmentMapLight`] from views and creates [`EnvironmentMapUniform`] for them.
///
/// Compared to the `ExtractComponentPlugin`, this implementation will create a default instance
/// if one does not already exist.
fn gather_environment_map_uniform(
view_query: Extract<Query<(RenderEntity, &EnvironmentMapLight), With<Camera3d>>>,
mut commands: Commands,
) {
for (view_entity, environment_map_light) in view_query.iter() {
let environment_map_uniform = EnvironmentMapUniform {
transform: Transform::from_rotation(environment_map_light.rotation)
.to_matrix()
.inverse(),
};
commands
.get_entity(view_entity)
.expect("Environment map light entity wasn't synced.")
.insert(environment_map_uniform);
}
}

/// Gathers up all light probes of a single type in the scene and assigns them
/// to views, performing frustum culling and distance sorting in the process.
fn gather_light_probes<C>(
Expand Down Expand Up @@ -511,29 +465,6 @@ fn gather_light_probes<C>(
}
}

/// Gathers up environment map settings for each applicable view and
/// writes them into a GPU buffer.
pub fn prepare_environment_uniform_buffer(
mut commands: Commands,
views: Query<(Entity, &EnvironmentMapUniform), With<ExtractedView>>,
mut environment_uniform_buffer: ResMut<EnvironmentMapUniformBuffer>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
) {
let Some(mut writer) =
environment_uniform_buffer.get_writer(views.iter().len(), &render_device, &render_queue)
else {
return;
};

for (view, environment_uniform) in views.iter() {
let uniform_offset = writer.write(environment_uniform);
commands
.entity(view)
.insert(ViewEnvironmentMapUniformOffset(uniform_offset));
}
}

// A system that runs after [`gather_light_probes`] and populates the GPU
// uniforms with the results.
//
Expand Down Expand Up @@ -606,6 +537,10 @@ fn upload_light_probes(
}
None => 1,
},
view_rotation: match maybe_view_light_probe_info {
Some(view_light_probe_info) => view_light_probe_info.rotation.inverse().into(),
None => Quat::IDENTITY.into(),
},
};

// Add any environment maps that [`gather_light_probes`] found to the
Expand Down Expand Up @@ -646,6 +581,7 @@ impl Default for LightProbesUniform {
smallest_specular_mip_level_for_view: 0,
intensity_for_view: 1.0,
view_environment_map_affects_lightmapped_mesh_diffuse: 1,
view_rotation: Quat::IDENTITY.into(),
}
}
}
Expand Down
Loading