From 9da18cce2a1cede5899086a126ec931f3a3727ab Mon Sep 17 00:00:00 2001 From: Sou1gh0st Date: Fri, 19 Jul 2024 23:00:50 +0800 Subject: [PATCH] Add support for environment map transformation (#14290) # Objective - Fixes: https://github.com/bevyengine/bevy/issues/14036 ## Solution - Add a world space transformation for the environment sample direction. ## Testing - I have tested the newly added `transform` field using the newly added `rotate_environment_map` example. https://github.com/user-attachments/assets/2de77c65-14bc-48ee-b76a-fb4e9782dbdb ## Migration Guide - Since we have added a new filed to the `EnvironmentMapLight` struct, users will need to include `..default()` or some rotation value in their initialization code. --- Cargo.toml | 12 ++ crates/bevy_pbr/src/deferred/mod.rs | 9 +- .../src/light_probe/environment_map.rs | 27 +++- .../src/light_probe/environment_map.wgsl | 33 ++++- crates/bevy_pbr/src/light_probe/mod.rs | 81 +++++++++++- .../src/meshlet/material_draw_nodes.rs | 7 +- crates/bevy_pbr/src/render/mesh.rs | 19 ++- .../bevy_pbr/src/render/mesh_view_bindings.rs | 41 +++--- .../src/render/mesh_view_bindings.wgsl | 29 ++-- .../bevy_pbr/src/render/mesh_view_types.wgsl | 5 + crates/bevy_pbr/src/ssr/mod.rs | 6 +- crates/bevy_pbr/src/volumetric_fog/render.rs | 8 +- examples/3d/animated_material.rs | 1 + examples/3d/anisotropy.rs | 3 +- examples/3d/anti_aliasing.rs | 1 + examples/3d/auto_exposure.rs | 2 +- examples/3d/clearcoat.rs | 3 +- examples/3d/color_grading.rs | 1 + examples/3d/deferred_rendering.rs | 1 + examples/3d/irradiance_volumes.rs | 2 +- examples/3d/load_gltf.rs | 1 + examples/3d/meshlet.rs | 1 + examples/3d/pbr.rs | 1 + examples/3d/post_processing.rs | 1 + examples/3d/reflection_probes.rs | 4 +- examples/3d/rotate_environment_map.rs | 124 ++++++++++++++++++ examples/3d/skybox.rs | 2 +- examples/3d/ssr.rs | 3 +- examples/3d/tonemapping.rs | 1 + examples/3d/transmission.rs | 1 + examples/3d/update_gltf_scene.rs | 1 + examples/3d/visibility_range.rs | 1 + examples/3d/volumetric_fog.rs | 2 +- examples/README.md | 1 + examples/tools/scene_viewer/main.rs | 1 + 35 files changed, 373 insertions(+), 63 deletions(-) create mode 100644 examples/3d/rotate_environment_map.rs diff --git a/Cargo.toml b/Cargo.toml index 456689e02d..61e0acfd14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3315,6 +3315,18 @@ description = "Demonstrates the built-in postprocessing features" category = "3D Rendering" wasm = true +[[example]] +name = "rotate_environment_map" +path = "examples/3d/rotate_environment_map.rs" +doc-scrape-examples = true +required-features = ["pbr_multi_layer_material_textures"] + +[package.metadata.example.rotate_environment_map] +name = "Rotate Environment Map" +description = "Demonstrates how to rotate the skybox and the environment map simultaneously" +category = "3D Rendering" +wasm = false + [profile.wasm-release] inherits = "release" opt-level = "z" diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index 4d7f247b82..eb4ba66cf0 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -1,7 +1,7 @@ use crate::{ graph::NodePbr, irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight, MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusionSettings, - ScreenSpaceReflectionsUniform, ViewLightProbesUniformOffset, + ScreenSpaceReflectionsUniform, ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset, ViewScreenSpaceReflectionsUniformOffset, }; use bevy_app::prelude::*; @@ -149,6 +149,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode { &'static ViewFogUniformOffset, &'static ViewLightProbesUniformOffset, &'static ViewScreenSpaceReflectionsUniformOffset, + &'static ViewEnvironmentMapUniformOffset, &'static MeshViewBindGroup, &'static ViewTarget, &'static DeferredLightingIdDepthTexture, @@ -165,6 +166,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode { view_fog_offset, view_light_probes_offset, view_ssr_offset, + view_environment_map_offset, mesh_view_bind_group, target, deferred_lighting_id_depth_texture, @@ -220,6 +222,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode { view_fog_offset.offset, **view_light_probes_offset, **view_ssr_offset, + **view_environment_map_offset, ], ); render_pass.set_bind_group(1, &bind_group_1, &[]); @@ -256,11 +259,11 @@ impl SpecializedRenderPipeline for DeferredLightingLayout { shader_defs.push("TONEMAP_IN_SHADER".into()); shader_defs.push(ShaderDefVal::UInt( "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), - 20, + 21, )); shader_defs.push(ShaderDefVal::UInt( "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), - 21, + 22, )); let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS); diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs index 2265287dc8..8a78e93083 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.rs +++ b/crates/bevy_pbr/src/light_probe/environment_map.rs @@ -50,13 +50,15 @@ use bevy_asset::{AssetId, Handle}; use bevy_ecs::{ bundle::Bundle, component::Component, query::QueryItem, system::lifetimeless::Read, }; +use bevy_math::Quat; use bevy_reflect::Reflect; use bevy_render::{ extract_instances::ExtractInstance, prelude::SpatialBundle, render_asset::RenderAssets, render_resource::{ - binding_types, BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, Shader, + binding_types::{self, uniform_buffer}, + BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, Shader, ShaderStages, TextureSampleType, TextureView, }, renderer::RenderDevice, @@ -67,7 +69,8 @@ use std::num::NonZeroU32; use std::ops::Deref; use crate::{ - add_cubemap_texture_view, binding_arrays_are_usable, LightProbe, MAX_VIEW_LIGHT_PROBES, + add_cubemap_texture_view, binding_arrays_are_usable, EnvironmentMapUniform, LightProbe, + MAX_VIEW_LIGHT_PROBES, }; use super::{LightProbeComponent, RenderViewLightProbes}; @@ -96,6 +99,22 @@ pub struct EnvironmentMapLight { /// /// See also . pub intensity: f32, + + /// 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. + pub rotation: Quat, +} + +impl Default for EnvironmentMapLight { + fn default() -> Self { + EnvironmentMapLight { + diffuse_map: Handle::default(), + specular_map: Handle::default(), + intensity: 0.0, + rotation: Quat::IDENTITY, + } + } } /// Like [`EnvironmentMapLight`], but contains asset IDs instead of handles. @@ -193,7 +212,7 @@ impl ExtractInstance for EnvironmentMapIds { /// specular binding arrays respectively, in addition to the sampler. pub(crate) fn get_bind_group_layout_entries( render_device: &RenderDevice, -) -> [BindGroupLayoutEntryBuilder; 3] { +) -> [BindGroupLayoutEntryBuilder; 4] { let mut texture_cube_binding = binding_types::texture_cube(TextureSampleType::Float { filterable: true }); if binding_arrays_are_usable(render_device) { @@ -205,6 +224,7 @@ pub(crate) fn get_bind_group_layout_entries( texture_cube_binding, texture_cube_binding, binding_types::sampler(SamplerBindingType::Filtering), + uniform_buffer::(true).visibility(ShaderStages::FRAGMENT), ] } @@ -312,6 +332,7 @@ impl LightProbeComponent for EnvironmentMapLight { diffuse_map: diffuse_map_handle, specular_map: specular_map_handle, intensity, + .. }) = view_component { if let (Some(_), Some(specular_map)) = ( diff --git a/crates/bevy_pbr/src/light_probe/environment_map.wgsl b/crates/bevy_pbr/src/light_probe/environment_map.wgsl index 7b9945a8a6..459ced93ec 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.wgsl +++ b/crates/bevy_pbr/src/light_probe/environment_map.wgsl @@ -3,6 +3,7 @@ #import bevy_pbr::light_probe::query_light_probe #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::lighting::{ F_Schlick_vec, LayerLightingInput, LightingInput, LAYER_BASE, LAYER_CLEARCOAT } @@ -57,17 +58,29 @@ fn compute_radiances( bindings::specular_environment_maps[query_result.texture_index]) - 1u); if (!found_diffuse_indirect) { + 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; + // Cube maps are left-handed so we negate the z coordinate. + irradiance_sample_dir.z = -irradiance_sample_dir.z; radiances.irradiance = textureSampleLevel( bindings::diffuse_environment_maps[query_result.texture_index], bindings::environment_map_sampler, - vec3(N.xy, -N.z), + irradiance_sample_dir, 0.0).rgb * query_result.intensity; } + var radiance_sample_dir = R; + // 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; + // Cube maps are left-handed so we negate the z coordinate. + radiance_sample_dir.z = -radiance_sample_dir.z; radiances.radiance = textureSampleLevel( bindings::specular_environment_maps[query_result.texture_index], bindings::environment_map_sampler, - vec3(R.xy, -R.z), + radiance_sample_dir, radiance_level).rgb * query_result.intensity; return radiances; @@ -102,17 +115,29 @@ fn compute_radiances( let intensity = light_probes.intensity_for_view; if (!found_diffuse_indirect) { + 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; + // Cube maps are left-handed so we negate the z coordinate. + irradiance_sample_dir.z = -irradiance_sample_dir.z; radiances.irradiance = textureSampleLevel( bindings::diffuse_environment_map, bindings::environment_map_sampler, - vec3(N.xy, -N.z), + irradiance_sample_dir, 0.0).rgb * intensity; } + var radiance_sample_dir = R; + // 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; + // Cube maps are left-handed so we negate the z coordinate. + radiance_sample_dir.z = -radiance_sample_dir.z; radiances.radiance = textureSampleLevel( bindings::specular_environment_map, bindings::environment_map_sampler, - vec3(R.xy, -R.z), + radiance_sample_dir, radiance_level).rgb * intensity; return radiances; diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs index 8c41f28280..dbcdd680d9 100644 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ b/crates/bevy_pbr/src/light_probe/mod.rs @@ -25,7 +25,7 @@ use bevy_render::{ view::ExtractedView, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_transform::prelude::GlobalTransform; +use bevy_transform::{components::Transform, prelude::GlobalTransform}; use bevy_utils::{tracing::error, HashMap}; use std::hash::Hash; @@ -296,6 +296,31 @@ impl LightProbe { } } +/// 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); + +/// 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_internal_asset!( @@ -330,15 +355,41 @@ impl Plugin for LightProbePlugin { render_app .add_plugins(ExtractInstancesPlugin::::new()) .init_resource::() + .init_resource::() + .add_systems(ExtractSchedule, gather_environment_map_uniform) .add_systems(ExtractSchedule, gather_light_probes::) .add_systems(ExtractSchedule, gather_light_probes::) .add_systems( Render, - upload_light_probes.in_set(RenderSet::PrepareResources), + (upload_light_probes, prepare_environment_uniform_buffer) + .in_set(RenderSet::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), With>>, + mut commands: Commands, +) { + for (view_entity, environment_map_light) in view_query.iter() { + let environment_map_uniform = if let Some(environment_map_light) = environment_map_light { + EnvironmentMapUniform { + transform: Transform::from_rotation(environment_map_light.rotation) + .compute_matrix() + .inverse(), + } + } else { + EnvironmentMapUniform::default() + }; + commands + .get_or_spawn(view_entity) + .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( @@ -395,6 +446,32 @@ fn gather_light_probes( } } +/// 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, Option<&EnvironmentMapUniform>), With>, + mut environment_uniform_buffer: ResMut, + render_device: Res, + render_queue: Res, +) { + 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 = match environment_uniform { + None => 0, + Some(environment_uniform) => 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. // diff --git a/crates/bevy_pbr/src/meshlet/material_draw_nodes.rs b/crates/bevy_pbr/src/meshlet/material_draw_nodes.rs index 153eafcc09..e7b71ea253 100644 --- a/crates/bevy_pbr/src/meshlet/material_draw_nodes.rs +++ b/crates/bevy_pbr/src/meshlet/material_draw_nodes.rs @@ -7,8 +7,8 @@ use super::{ MeshletGpuScene, }; use crate::{ - MeshViewBindGroup, PrepassViewBindGroup, ViewFogUniformOffset, ViewLightProbesUniformOffset, - ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset, + MeshViewBindGroup, PrepassViewBindGroup, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, + ViewLightProbesUniformOffset, ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset, }; use bevy_core_pipeline::prepass::{ MotionVectorPrepass, PreviousViewUniformOffset, ViewPrepassTextures, @@ -41,6 +41,7 @@ impl ViewNode for MeshletMainOpaquePass3dNode { &'static ViewFogUniformOffset, &'static ViewLightProbesUniformOffset, &'static ViewScreenSpaceReflectionsUniformOffset, + &'static ViewEnvironmentMapUniformOffset, &'static MeshletViewMaterialsMainOpaquePass, &'static MeshletViewBindGroups, &'static MeshletViewResources, @@ -59,6 +60,7 @@ impl ViewNode for MeshletMainOpaquePass3dNode { view_fog_offset, view_light_probes_offset, view_ssr_offset, + view_environment_map_offset, meshlet_view_materials, meshlet_view_bind_groups, meshlet_view_resources, @@ -111,6 +113,7 @@ impl ViewNode for MeshletMainOpaquePass3dNode { view_fog_offset.offset, **view_light_probes_offset, **view_ssr_offset, + **view_environment_map_offset, ], ); render_pass.set_bind_group(1, meshlet_material_draw_bind_group, &[]); diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 6df3849cab..56f1cd1289 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1774,11 +1774,11 @@ impl SpecializedMeshPipeline for MeshPipeline { shader_defs.push("TONEMAP_IN_SHADER".into()); shader_defs.push(ShaderDefVal::UInt( "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), - 20, + 21, )); shader_defs.push(ShaderDefVal::UInt( "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), - 21, + 22, )); let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS); @@ -2105,6 +2105,7 @@ impl RenderCommand

for SetMeshViewBindGroup Read, Read, Read, + Read, Read, ); type ItemQuery = (); @@ -2112,10 +2113,15 @@ impl RenderCommand

for SetMeshViewBindGroup #[inline] fn render<'w>( _item: &P, - (view_uniform, view_lights, view_fog, view_light_probes, view_ssr, mesh_view_bind_group): ROQueryItem< - 'w, - Self::ViewQuery, - >, + ( + view_uniform, + view_lights, + view_fog, + view_light_probes, + view_ssr, + view_environment_map, + mesh_view_bind_group, + ): ROQueryItem<'w, Self::ViewQuery>, _entity: Option<()>, _: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, @@ -2129,6 +2135,7 @@ impl RenderCommand

for SetMeshViewBindGroup view_fog.offset, **view_light_probes, **view_ssr, + **view_environment_map, ], ); diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 465fac8011..adc1802a47 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -41,11 +41,11 @@ use crate::{ self, IrradianceVolume, RenderViewIrradianceVolumeBindGroupEntries, IRRADIANCE_VOLUMES_ARE_USABLE, }, - prepass, FogMeta, GlobalClusterableObjectMeta, GpuClusterableObjects, GpuFog, GpuLights, - LightMeta, LightProbesBuffer, LightProbesUniform, MeshPipeline, MeshPipelineKey, - RenderViewLightProbes, ScreenSpaceAmbientOcclusionTextures, ScreenSpaceReflectionsBuffer, - ScreenSpaceReflectionsUniform, ShadowSamplers, ViewClusterBindings, ViewShadowBindings, - CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, + prepass, EnvironmentMapUniformBuffer, FogMeta, GlobalClusterableObjectMeta, + GpuClusterableObjects, GpuFog, GpuLights, LightMeta, LightProbesBuffer, LightProbesUniform, + MeshPipeline, MeshPipelineKey, RenderViewLightProbes, ScreenSpaceAmbientOcclusionTextures, + ScreenSpaceReflectionsBuffer, ScreenSpaceReflectionsUniform, ShadowSamplers, + ViewClusterBindings, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, }; #[derive(Clone)] @@ -299,6 +299,7 @@ fn layout_entries( (15, environment_map_entries[0]), (16, environment_map_entries[1]), (17, environment_map_entries[2]), + (18, environment_map_entries[3]), )); // Irradiance volumes @@ -306,16 +307,16 @@ fn layout_entries( let irradiance_volume_entries = irradiance_volume::get_bind_group_layout_entries(render_device); entries = entries.extend_with_indices(( - (18, irradiance_volume_entries[0]), - (19, irradiance_volume_entries[1]), + (19, irradiance_volume_entries[0]), + (20, irradiance_volume_entries[1]), )); } // Tonemapping let tonemapping_lut_entries = get_lut_bind_group_layout_entries(); entries = entries.extend_with_indices(( - (20, tonemapping_lut_entries[0]), - (21, tonemapping_lut_entries[1]), + (21, tonemapping_lut_entries[0]), + (22, tonemapping_lut_entries[1]), )); // Prepass @@ -325,7 +326,7 @@ fn layout_entries( { for (entry, binding) in prepass::get_bind_group_layout_entries(layout_key) .iter() - .zip([22, 23, 24, 25]) + .zip([23, 24, 25, 26]) { if let Some(entry) = entry { entries = entries.extend_with_indices(((binding as u32, *entry),)); @@ -336,10 +337,10 @@ fn layout_entries( // View Transmission Texture entries = entries.extend_with_indices(( ( - 26, + 27, texture_2d(TextureSampleType::Float { filterable: true }), ), - (27, sampler(SamplerBindingType::Filtering)), + (28, sampler(SamplerBindingType::Filtering)), )); entries.to_vec() @@ -450,7 +451,7 @@ pub fn prepare_mesh_view_bind_groups( light_meta: Res, global_light_meta: Res, fog_meta: Res, - view_uniforms: Res, + (view_uniforms, environment_map_uniform): (Res, Res), views: Query<( Entity, &ViewShadowBindings, @@ -484,6 +485,7 @@ pub fn prepare_mesh_view_bind_groups( Some(light_probes_binding), Some(visibility_ranges_buffer), Some(ssr_binding), + Some(environment_map_binding), ) = ( view_uniforms.uniforms.binding(), light_meta.view_gpu_lights.binding(), @@ -493,6 +495,7 @@ pub fn prepare_mesh_view_bind_groups( light_probes_buffer.binding(), visibility_ranges.buffer().buffer(), ssr_buffer.binding(), + environment_map_uniform.binding(), ) { for ( entity, @@ -559,6 +562,7 @@ pub fn prepare_mesh_view_bind_groups( (15, diffuse_texture_view), (16, specular_texture_view), (17, sampler), + (18, environment_map_binding.clone()), )); } RenderViewEnvironmentMapBindGroupEntries::Multiple { @@ -570,6 +574,7 @@ pub fn prepare_mesh_view_bind_groups( (15, diffuse_texture_views.as_slice()), (16, specular_texture_views.as_slice()), (17, sampler), + (18, environment_map_binding.clone()), )); } } @@ -590,21 +595,21 @@ pub fn prepare_mesh_view_bind_groups( texture_view, sampler, }) => { - entries = entries.extend_with_indices(((18, texture_view), (19, sampler))); + entries = entries.extend_with_indices(((19, texture_view), (20, sampler))); } Some(RenderViewIrradianceVolumeBindGroupEntries::Multiple { ref texture_views, sampler, }) => { entries = entries - .extend_with_indices(((18, texture_views.as_slice()), (19, sampler))); + .extend_with_indices(((19, texture_views.as_slice()), (20, sampler))); } None => {} } let lut_bindings = get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image); - entries = entries.extend_with_indices(((20, lut_bindings.0), (21, lut_bindings.1))); + entries = entries.extend_with_indices(((21, lut_bindings.0), (22, lut_bindings.1))); // When using WebGL, we can't have a depth texture with multisampling let prepass_bindings; @@ -614,7 +619,7 @@ pub fn prepare_mesh_view_bind_groups( for (binding, index) in prepass_bindings .iter() .map(Option::as_ref) - .zip([22, 23, 24, 25]) + .zip([23, 24, 25, 26]) .flat_map(|(b, i)| b.map(|b| (b, i))) { entries = entries.extend_with_indices(((index, binding),)); @@ -630,7 +635,7 @@ pub fn prepare_mesh_view_bind_groups( .unwrap_or(&fallback_image_zero.sampler); entries = - entries.extend_with_indices(((26, transmission_view), (27, transmission_sampler))); + entries.extend_with_indices(((27, transmission_view), (28, transmission_sampler))); commands.entity(entity).insert(MeshViewBindGroup { value: render_device.create_bind_group("mesh_view_bind_group", layout, &entries), diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index f5fcd34271..5036e81673 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -53,47 +53,48 @@ const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: u32 = 64u; @group(0) @binding(16) var specular_environment_map: texture_cube; #endif @group(0) @binding(17) var environment_map_sampler: sampler; +@group(0) @binding(18) var environment_map_uniform: types::EnvironmentMapUniform; #ifdef IRRADIANCE_VOLUMES_ARE_USABLE #ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY -@group(0) @binding(18) var irradiance_volumes: binding_array, 8u>; +@group(0) @binding(19) var irradiance_volumes: binding_array, 8u>; #else -@group(0) @binding(18) var irradiance_volume: texture_3d; +@group(0) @binding(19) var irradiance_volume: texture_3d; #endif -@group(0) @binding(19) var irradiance_volume_sampler: sampler; +@group(0) @binding(20) var irradiance_volume_sampler: sampler; #endif -@group(0) @binding(20) var dt_lut_texture: texture_3d; -@group(0) @binding(21) var dt_lut_sampler: sampler; +@group(0) @binding(21) var dt_lut_texture: texture_3d; +@group(0) @binding(22) var dt_lut_sampler: sampler; #ifdef MULTISAMPLED #ifdef DEPTH_PREPASS -@group(0) @binding(22) var depth_prepass_texture: texture_depth_multisampled_2d; +@group(0) @binding(23) var depth_prepass_texture: texture_depth_multisampled_2d; #endif // DEPTH_PREPASS #ifdef NORMAL_PREPASS -@group(0) @binding(23) var normal_prepass_texture: texture_multisampled_2d; +@group(0) @binding(24) var normal_prepass_texture: texture_multisampled_2d; #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS -@group(0) @binding(24) var motion_vector_prepass_texture: texture_multisampled_2d; +@group(0) @binding(25) var motion_vector_prepass_texture: texture_multisampled_2d; #endif // MOTION_VECTOR_PREPASS #else // MULTISAMPLED #ifdef DEPTH_PREPASS -@group(0) @binding(22) var depth_prepass_texture: texture_depth_2d; +@group(0) @binding(23) var depth_prepass_texture: texture_depth_2d; #endif // DEPTH_PREPASS #ifdef NORMAL_PREPASS -@group(0) @binding(23) var normal_prepass_texture: texture_2d; +@group(0) @binding(24) var normal_prepass_texture: texture_2d; #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS -@group(0) @binding(24) var motion_vector_prepass_texture: texture_2d; +@group(0) @binding(25) var motion_vector_prepass_texture: texture_2d; #endif // MOTION_VECTOR_PREPASS #endif // MULTISAMPLED #ifdef DEFERRED_PREPASS -@group(0) @binding(25) var deferred_prepass_texture: texture_2d; +@group(0) @binding(26) var deferred_prepass_texture: texture_2d; #endif // DEFERRED_PREPASS -@group(0) @binding(26) var view_transmission_texture: texture_2d; -@group(0) @binding(27) var view_transmission_sampler: sampler; +@group(0) @binding(27) var view_transmission_texture: texture_2d; +@group(0) @binding(28) var view_transmission_sampler: sampler; diff --git a/crates/bevy_pbr/src/render/mesh_view_types.wgsl b/crates/bevy_pbr/src/render/mesh_view_types.wgsl index def738b3e2..c1d379e3b4 100644 --- a/crates/bevy_pbr/src/render/mesh_view_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_types.wgsl @@ -148,3 +148,8 @@ struct ScreenSpaceReflectionsSettings { bisection_steps: u32, use_secant: u32, }; + +struct EnvironmentMapUniform { + // Transformation matrix for the environment cubemaps in world space. + transform: mat4x4, +}; \ No newline at end of file diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index 399c7820e5..1ae86b10a1 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -43,7 +43,8 @@ use bevy_utils::{info_once, prelude::default}; use crate::{ binding_arrays_are_usable, graph::NodePbr, prelude::EnvironmentMapLight, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, RenderViewLightProbes, - ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset, + ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset, + ViewLightsUniformOffset, }; const SSR_SHADER_HANDLE: Handle = Handle::weak_from_u128(10438925299917978850); @@ -258,6 +259,7 @@ impl ViewNode for ScreenSpaceReflectionsNode { Read, Read, Read, + Read, Read, Read, ); @@ -273,6 +275,7 @@ impl ViewNode for ScreenSpaceReflectionsNode { view_fog_offset, view_light_probes_offset, view_ssr_offset, + view_environment_map_offset, view_bind_group, ssr_pipeline_id, ): QueryItem<'w, Self::ViewQuery>, @@ -324,6 +327,7 @@ impl ViewNode for ScreenSpaceReflectionsNode { view_fog_offset.offset, **view_light_probes_offset, **view_ssr_offset, + **view_environment_map_offset, ], ); diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index 1c2cfc788c..735a9653dd 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -46,8 +46,9 @@ use bitflags::bitflags; use crate::{ FogVolume, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, - ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset, - ViewScreenSpaceReflectionsUniformOffset, VolumetricFogSettings, VolumetricLight, + ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset, + ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset, VolumetricFogSettings, + VolumetricLight, }; bitflags! { @@ -306,6 +307,7 @@ impl ViewNode for VolumetricFogNode { Read, Read, Read, + Read, ); fn run<'w>( @@ -323,6 +325,7 @@ impl ViewNode for VolumetricFogNode { view_fog_volumes, view_bind_group, view_ssr_offset, + view_environment_map_offset, ): QueryItem<'w, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { @@ -445,6 +448,7 @@ impl ViewNode for VolumetricFogNode { view_fog_offset.offset, **view_light_probes_offset, **view_ssr_offset, + **view_environment_map_offset, ], ); render_pass.set_bind_group( diff --git a/examples/3d/animated_material.rs b/examples/3d/animated_material.rs index ecf18cea04..131284568d 100644 --- a/examples/3d/animated_material.rs +++ b/examples/3d/animated_material.rs @@ -26,6 +26,7 @@ fn setup( diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), intensity: 2_000.0, + ..default() }, )); diff --git a/examples/3d/anisotropy.rs b/examples/3d/anisotropy.rs index 047f27f412..84583c19b1 100644 --- a/examples/3d/anisotropy.rs +++ b/examples/3d/anisotropy.rs @@ -240,12 +240,13 @@ fn add_skybox_and_environment_map( .insert(Skybox { brightness: 5000.0, image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), - ..Default::default() + ..default() }) .insert(EnvironmentMapLight { diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), intensity: 2500.0, + ..default() }); } diff --git a/examples/3d/anti_aliasing.rs b/examples/3d/anti_aliasing.rs index 7b04a499a0..4738cf9c8b 100644 --- a/examples/3d/anti_aliasing.rs +++ b/examples/3d/anti_aliasing.rs @@ -324,6 +324,7 @@ fn setup( diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), intensity: 150.0, + ..default() }, FogSettings { color: Color::srgba_u8(43, 44, 47, 255), diff --git a/examples/3d/auto_exposure.rs b/examples/3d/auto_exposure.rs index 7d03337867..49ef4ea619 100644 --- a/examples/3d/auto_exposure.rs +++ b/examples/3d/auto_exposure.rs @@ -54,7 +54,7 @@ fn setup( Skybox { image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), brightness: bevy::pbr::light_consts::lux::DIRECT_SUNLIGHT, - ..Default::default() + ..default() }, )); diff --git a/examples/3d/clearcoat.rs b/examples/3d/clearcoat.rs index 951bc0452d..69aaa30717 100644 --- a/examples/3d/clearcoat.rs +++ b/examples/3d/clearcoat.rs @@ -224,12 +224,13 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) { .insert(Skybox { brightness: 5000.0, image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), - ..Default::default() + ..default() }) .insert(EnvironmentMapLight { diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), intensity: 2000.0, + ..default() }); } diff --git a/examples/3d/color_grading.rs b/examples/3d/color_grading.rs index cebaadafef..6ed6d12d8a 100644 --- a/examples/3d/color_grading.rs +++ b/examples/3d/color_grading.rs @@ -374,6 +374,7 @@ fn add_camera(commands: &mut Commands, asset_server: &AssetServer, color_grading diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), intensity: 2000.0, + ..default() }, )); } diff --git a/examples/3d/deferred_rendering.rs b/examples/3d/deferred_rendering.rs index 7bd5f8937b..5ed07ab344 100644 --- a/examples/3d/deferred_rendering.rs +++ b/examples/3d/deferred_rendering.rs @@ -56,6 +56,7 @@ fn setup( diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), intensity: 2000.0, + ..default() }, DepthPrepass, MotionVectorPrepass, diff --git a/examples/3d/irradiance_volumes.rs b/examples/3d/irradiance_volumes.rs index 6374590025..0c4c44f00b 100644 --- a/examples/3d/irradiance_volumes.rs +++ b/examples/3d/irradiance_volumes.rs @@ -239,7 +239,7 @@ fn spawn_camera(commands: &mut Commands, assets: &ExampleAssets) { .insert(Skybox { image: assets.skybox.clone(), brightness: 150.0, - ..Default::default() + ..default() }); } diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs index 358446238e..b8579c76fa 100644 --- a/examples/3d/load_gltf.rs +++ b/examples/3d/load_gltf.rs @@ -26,6 +26,7 @@ fn setup(mut commands: Commands, asset_server: Res) { diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), intensity: 250.0, + ..default() }, )); diff --git a/examples/3d/meshlet.rs b/examples/3d/meshlet.rs index 24b3979f38..e982656e11 100644 --- a/examples/3d/meshlet.rs +++ b/examples/3d/meshlet.rs @@ -56,6 +56,7 @@ fn setup( diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), intensity: 150.0, + ..default() }, CameraController::default(), )); diff --git a/examples/3d/pbr.rs b/examples/3d/pbr.rs index e8e4d0dc8c..3c7a762119 100644 --- a/examples/3d/pbr.rs +++ b/examples/3d/pbr.rs @@ -130,6 +130,7 @@ fn setup( diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), intensity: 900.0, + ..default() }, )); } diff --git a/examples/3d/post_processing.rs b/examples/3d/post_processing.rs index cb582c81fe..db011aa7fe 100644 --- a/examples/3d/post_processing.rs +++ b/examples/3d/post_processing.rs @@ -80,6 +80,7 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) { diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), intensity: 2000.0, + ..default() }, // Include the `ChromaticAberration` component. ChromaticAberration::default(), diff --git a/examples/3d/reflection_probes.rs b/examples/3d/reflection_probes.rs index 819287d577..ca8f02441d 100644 --- a/examples/3d/reflection_probes.rs +++ b/examples/3d/reflection_probes.rs @@ -151,6 +151,7 @@ fn spawn_reflection_probe(commands: &mut Commands, cubemaps: &Cubemaps) { diffuse_map: cubemaps.diffuse.clone(), specular_map: cubemaps.specular_reflection_probe.clone(), intensity: 5000.0, + ..default() }, }); } @@ -187,7 +188,7 @@ fn add_environment_map_to_camera( .insert(Skybox { image: cubemaps.skybox.clone(), brightness: 5000.0, - ..Default::default() + ..default() }); } } @@ -299,6 +300,7 @@ fn create_camera_environment_map_light(cubemaps: &Cubemaps) -> EnvironmentMapLig diffuse_map: cubemaps.diffuse.clone(), specular_map: cubemaps.specular_environment_map.clone(), intensity: 5000.0, + ..default() } } diff --git a/examples/3d/rotate_environment_map.rs b/examples/3d/rotate_environment_map.rs new file mode 100644 index 0000000000..dd12895929 --- /dev/null +++ b/examples/3d/rotate_environment_map.rs @@ -0,0 +1,124 @@ +//! Demonstrates how to rotate the skybox and the environment map simultaneously. + +use std::f32::consts::PI; + +use bevy::{ + color::palettes::css::{GOLD, WHITE}, + core_pipeline::{tonemapping::Tonemapping::AcesFitted, Skybox}, + prelude::*, + render::texture::ImageLoaderSettings, +}; + +/// Entry point. +pub fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, rotate_skybox_and_environment_map) + .run(); +} + +/// Initializes the scene. +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + asset_server: Res, +) { + let sphere_mesh = create_sphere_mesh(&mut meshes); + spawn_sphere(&mut commands, &mut materials, &asset_server, &sphere_mesh); + spawn_light(&mut commands); + spawn_camera(&mut commands, &asset_server); +} + +/// Rotate the skybox and the environment map per frame. +fn rotate_skybox_and_environment_map( + mut environments: Query<(&mut Skybox, &mut EnvironmentMapLight)>, + time: Res