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.
This commit is contained in:
Sou1gh0st 2024-07-19 23:00:50 +08:00 committed by GitHub
parent d8d49fdd13
commit 9da18cce2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 373 additions and 63 deletions

View file

@ -3315,6 +3315,18 @@ description = "Demonstrates the built-in postprocessing features"
category = "3D Rendering" category = "3D Rendering"
wasm = true 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] [profile.wasm-release]
inherits = "release" inherits = "release"
opt-level = "z" opt-level = "z"

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
graph::NodePbr, irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight, graph::NodePbr, irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight,
MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusionSettings, MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusionSettings,
ScreenSpaceReflectionsUniform, ViewLightProbesUniformOffset, ScreenSpaceReflectionsUniform, ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset,
ViewScreenSpaceReflectionsUniformOffset, ViewScreenSpaceReflectionsUniformOffset,
}; };
use bevy_app::prelude::*; use bevy_app::prelude::*;
@ -149,6 +149,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
&'static ViewFogUniformOffset, &'static ViewFogUniformOffset,
&'static ViewLightProbesUniformOffset, &'static ViewLightProbesUniformOffset,
&'static ViewScreenSpaceReflectionsUniformOffset, &'static ViewScreenSpaceReflectionsUniformOffset,
&'static ViewEnvironmentMapUniformOffset,
&'static MeshViewBindGroup, &'static MeshViewBindGroup,
&'static ViewTarget, &'static ViewTarget,
&'static DeferredLightingIdDepthTexture, &'static DeferredLightingIdDepthTexture,
@ -165,6 +166,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
view_fog_offset, view_fog_offset,
view_light_probes_offset, view_light_probes_offset,
view_ssr_offset, view_ssr_offset,
view_environment_map_offset,
mesh_view_bind_group, mesh_view_bind_group,
target, target,
deferred_lighting_id_depth_texture, deferred_lighting_id_depth_texture,
@ -220,6 +222,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
view_fog_offset.offset, view_fog_offset.offset,
**view_light_probes_offset, **view_light_probes_offset,
**view_ssr_offset, **view_ssr_offset,
**view_environment_map_offset,
], ],
); );
render_pass.set_bind_group(1, &bind_group_1, &[]); 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("TONEMAP_IN_SHADER".into());
shader_defs.push(ShaderDefVal::UInt( shader_defs.push(ShaderDefVal::UInt(
"TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(),
20, 21,
)); ));
shader_defs.push(ShaderDefVal::UInt( shader_defs.push(ShaderDefVal::UInt(
"TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(),
21, 22,
)); ));
let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS); let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS);

View file

@ -50,13 +50,15 @@ use bevy_asset::{AssetId, Handle};
use bevy_ecs::{ use bevy_ecs::{
bundle::Bundle, component::Component, query::QueryItem, system::lifetimeless::Read, bundle::Bundle, component::Component, query::QueryItem, system::lifetimeless::Read,
}; };
use bevy_math::Quat;
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_render::{ use bevy_render::{
extract_instances::ExtractInstance, extract_instances::ExtractInstance,
prelude::SpatialBundle, prelude::SpatialBundle,
render_asset::RenderAssets, render_asset::RenderAssets,
render_resource::{ render_resource::{
binding_types, BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, Shader, binding_types::{self, uniform_buffer},
BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, Shader, ShaderStages,
TextureSampleType, TextureView, TextureSampleType, TextureView,
}, },
renderer::RenderDevice, renderer::RenderDevice,
@ -67,7 +69,8 @@ use std::num::NonZeroU32;
use std::ops::Deref; use std::ops::Deref;
use crate::{ 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}; use super::{LightProbeComponent, RenderViewLightProbes};
@ -96,6 +99,22 @@ pub struct EnvironmentMapLight {
/// ///
/// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>. /// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.
pub intensity: f32, 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. /// 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. /// specular binding arrays respectively, in addition to the sampler.
pub(crate) fn get_bind_group_layout_entries( pub(crate) fn get_bind_group_layout_entries(
render_device: &RenderDevice, render_device: &RenderDevice,
) -> [BindGroupLayoutEntryBuilder; 3] { ) -> [BindGroupLayoutEntryBuilder; 4] {
let mut texture_cube_binding = let mut texture_cube_binding =
binding_types::texture_cube(TextureSampleType::Float { filterable: true }); binding_types::texture_cube(TextureSampleType::Float { filterable: true });
if binding_arrays_are_usable(render_device) { 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,
texture_cube_binding, texture_cube_binding,
binding_types::sampler(SamplerBindingType::Filtering), binding_types::sampler(SamplerBindingType::Filtering),
uniform_buffer::<EnvironmentMapUniform>(true).visibility(ShaderStages::FRAGMENT),
] ]
} }
@ -312,6 +332,7 @@ impl LightProbeComponent for EnvironmentMapLight {
diffuse_map: diffuse_map_handle, diffuse_map: diffuse_map_handle,
specular_map: specular_map_handle, specular_map: specular_map_handle,
intensity, intensity,
..
}) = view_component }) = view_component
{ {
if let (Some(_), Some(specular_map)) = ( if let (Some(_), Some(specular_map)) = (

View file

@ -3,6 +3,7 @@
#import bevy_pbr::light_probe::query_light_probe #import bevy_pbr::light_probe::query_light_probe
#import bevy_pbr::mesh_view_bindings as bindings #import bevy_pbr::mesh_view_bindings as bindings
#import bevy_pbr::mesh_view_bindings::light_probes #import bevy_pbr::mesh_view_bindings::light_probes
#import bevy_pbr::mesh_view_bindings::environment_map_uniform
#import bevy_pbr::lighting::{ #import bevy_pbr::lighting::{
F_Schlick_vec, LayerLightingInput, LightingInput, LAYER_BASE, LAYER_CLEARCOAT 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); bindings::specular_environment_maps[query_result.texture_index]) - 1u);
if (!found_diffuse_indirect) { 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( radiances.irradiance = textureSampleLevel(
bindings::diffuse_environment_maps[query_result.texture_index], bindings::diffuse_environment_maps[query_result.texture_index],
bindings::environment_map_sampler, bindings::environment_map_sampler,
vec3(N.xy, -N.z), irradiance_sample_dir,
0.0).rgb * query_result.intensity; 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( radiances.radiance = textureSampleLevel(
bindings::specular_environment_maps[query_result.texture_index], bindings::specular_environment_maps[query_result.texture_index],
bindings::environment_map_sampler, bindings::environment_map_sampler,
vec3(R.xy, -R.z), radiance_sample_dir,
radiance_level).rgb * query_result.intensity; radiance_level).rgb * query_result.intensity;
return radiances; return radiances;
@ -102,17 +115,29 @@ fn compute_radiances(
let intensity = light_probes.intensity_for_view; let intensity = light_probes.intensity_for_view;
if (!found_diffuse_indirect) { 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( radiances.irradiance = textureSampleLevel(
bindings::diffuse_environment_map, bindings::diffuse_environment_map,
bindings::environment_map_sampler, bindings::environment_map_sampler,
vec3(N.xy, -N.z), irradiance_sample_dir,
0.0).rgb * intensity; 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( radiances.radiance = textureSampleLevel(
bindings::specular_environment_map, bindings::specular_environment_map,
bindings::environment_map_sampler, bindings::environment_map_sampler,
vec3(R.xy, -R.z), radiance_sample_dir,
radiance_level).rgb * intensity; radiance_level).rgb * intensity;
return radiances; return radiances;

View file

@ -25,7 +25,7 @@ use bevy_render::{
view::ExtractedView, view::ExtractedView,
Extract, ExtractSchedule, Render, RenderApp, RenderSet, Extract, ExtractSchedule, Render, RenderApp, RenderSet,
}; };
use bevy_transform::prelude::GlobalTransform; use bevy_transform::{components::Transform, prelude::GlobalTransform};
use bevy_utils::{tracing::error, HashMap}; use bevy_utils::{tracing::error, HashMap};
use std::hash::Hash; 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<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 { impl Plugin for LightProbePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
load_internal_asset!( load_internal_asset!(
@ -330,15 +355,41 @@ impl Plugin for LightProbePlugin {
render_app render_app
.add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new()) .add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new())
.init_resource::<LightProbesBuffer>() .init_resource::<LightProbesBuffer>()
.init_resource::<EnvironmentMapUniformBuffer>()
.add_systems(ExtractSchedule, gather_environment_map_uniform)
.add_systems(ExtractSchedule, gather_light_probes::<EnvironmentMapLight>) .add_systems(ExtractSchedule, gather_light_probes::<EnvironmentMapLight>)
.add_systems(ExtractSchedule, gather_light_probes::<IrradianceVolume>) .add_systems(ExtractSchedule, gather_light_probes::<IrradianceVolume>)
.add_systems( .add_systems(
Render, 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<Query<(Entity, Option<&EnvironmentMapLight>), With<Camera3d>>>,
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 /// 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. /// to views, performing frustum culling and distance sorting in the process.
fn gather_light_probes<C>( fn gather_light_probes<C>(
@ -395,6 +446,32 @@ 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, Option<&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 = 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 // A system that runs after [`gather_light_probes`] and populates the GPU
// uniforms with the results. // uniforms with the results.
// //

View file

@ -7,8 +7,8 @@ use super::{
MeshletGpuScene, MeshletGpuScene,
}; };
use crate::{ use crate::{
MeshViewBindGroup, PrepassViewBindGroup, ViewFogUniformOffset, ViewLightProbesUniformOffset, MeshViewBindGroup, PrepassViewBindGroup, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset,
ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset,
}; };
use bevy_core_pipeline::prepass::{ use bevy_core_pipeline::prepass::{
MotionVectorPrepass, PreviousViewUniformOffset, ViewPrepassTextures, MotionVectorPrepass, PreviousViewUniformOffset, ViewPrepassTextures,
@ -41,6 +41,7 @@ impl ViewNode for MeshletMainOpaquePass3dNode {
&'static ViewFogUniformOffset, &'static ViewFogUniformOffset,
&'static ViewLightProbesUniformOffset, &'static ViewLightProbesUniformOffset,
&'static ViewScreenSpaceReflectionsUniformOffset, &'static ViewScreenSpaceReflectionsUniformOffset,
&'static ViewEnvironmentMapUniformOffset,
&'static MeshletViewMaterialsMainOpaquePass, &'static MeshletViewMaterialsMainOpaquePass,
&'static MeshletViewBindGroups, &'static MeshletViewBindGroups,
&'static MeshletViewResources, &'static MeshletViewResources,
@ -59,6 +60,7 @@ impl ViewNode for MeshletMainOpaquePass3dNode {
view_fog_offset, view_fog_offset,
view_light_probes_offset, view_light_probes_offset,
view_ssr_offset, view_ssr_offset,
view_environment_map_offset,
meshlet_view_materials, meshlet_view_materials,
meshlet_view_bind_groups, meshlet_view_bind_groups,
meshlet_view_resources, meshlet_view_resources,
@ -111,6 +113,7 @@ impl ViewNode for MeshletMainOpaquePass3dNode {
view_fog_offset.offset, view_fog_offset.offset,
**view_light_probes_offset, **view_light_probes_offset,
**view_ssr_offset, **view_ssr_offset,
**view_environment_map_offset,
], ],
); );
render_pass.set_bind_group(1, meshlet_material_draw_bind_group, &[]); render_pass.set_bind_group(1, meshlet_material_draw_bind_group, &[]);

View file

@ -1774,11 +1774,11 @@ impl SpecializedMeshPipeline for MeshPipeline {
shader_defs.push("TONEMAP_IN_SHADER".into()); shader_defs.push("TONEMAP_IN_SHADER".into());
shader_defs.push(ShaderDefVal::UInt( shader_defs.push(ShaderDefVal::UInt(
"TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(),
20, 21,
)); ));
shader_defs.push(ShaderDefVal::UInt( shader_defs.push(ShaderDefVal::UInt(
"TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(),
21, 22,
)); ));
let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS); let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS);
@ -2105,6 +2105,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I>
Read<ViewFogUniformOffset>, Read<ViewFogUniformOffset>,
Read<ViewLightProbesUniformOffset>, Read<ViewLightProbesUniformOffset>,
Read<ViewScreenSpaceReflectionsUniformOffset>, Read<ViewScreenSpaceReflectionsUniformOffset>,
Read<ViewEnvironmentMapUniformOffset>,
Read<MeshViewBindGroup>, Read<MeshViewBindGroup>,
); );
type ItemQuery = (); type ItemQuery = ();
@ -2112,10 +2113,15 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I>
#[inline] #[inline]
fn render<'w>( fn render<'w>(
_item: &P, _item: &P,
(view_uniform, view_lights, view_fog, view_light_probes, view_ssr, mesh_view_bind_group): ROQueryItem< (
'w, view_uniform,
Self::ViewQuery, view_lights,
>, view_fog,
view_light_probes,
view_ssr,
view_environment_map,
mesh_view_bind_group,
): ROQueryItem<'w, Self::ViewQuery>,
_entity: Option<()>, _entity: Option<()>,
_: SystemParamItem<'w, '_, Self::Param>, _: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>, pass: &mut TrackedRenderPass<'w>,
@ -2129,6 +2135,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I>
view_fog.offset, view_fog.offset,
**view_light_probes, **view_light_probes,
**view_ssr, **view_ssr,
**view_environment_map,
], ],
); );

View file

@ -41,11 +41,11 @@ use crate::{
self, IrradianceVolume, RenderViewIrradianceVolumeBindGroupEntries, self, IrradianceVolume, RenderViewIrradianceVolumeBindGroupEntries,
IRRADIANCE_VOLUMES_ARE_USABLE, IRRADIANCE_VOLUMES_ARE_USABLE,
}, },
prepass, FogMeta, GlobalClusterableObjectMeta, GpuClusterableObjects, GpuFog, GpuLights, prepass, EnvironmentMapUniformBuffer, FogMeta, GlobalClusterableObjectMeta,
LightMeta, LightProbesBuffer, LightProbesUniform, MeshPipeline, MeshPipelineKey, GpuClusterableObjects, GpuFog, GpuLights, LightMeta, LightProbesBuffer, LightProbesUniform,
RenderViewLightProbes, ScreenSpaceAmbientOcclusionTextures, ScreenSpaceReflectionsBuffer, MeshPipeline, MeshPipelineKey, RenderViewLightProbes, ScreenSpaceAmbientOcclusionTextures,
ScreenSpaceReflectionsUniform, ShadowSamplers, ViewClusterBindings, ViewShadowBindings, ScreenSpaceReflectionsBuffer, ScreenSpaceReflectionsUniform, ShadowSamplers,
CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, ViewClusterBindings, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -299,6 +299,7 @@ fn layout_entries(
(15, environment_map_entries[0]), (15, environment_map_entries[0]),
(16, environment_map_entries[1]), (16, environment_map_entries[1]),
(17, environment_map_entries[2]), (17, environment_map_entries[2]),
(18, environment_map_entries[3]),
)); ));
// Irradiance volumes // Irradiance volumes
@ -306,16 +307,16 @@ fn layout_entries(
let irradiance_volume_entries = let irradiance_volume_entries =
irradiance_volume::get_bind_group_layout_entries(render_device); irradiance_volume::get_bind_group_layout_entries(render_device);
entries = entries.extend_with_indices(( entries = entries.extend_with_indices((
(18, irradiance_volume_entries[0]), (19, irradiance_volume_entries[0]),
(19, irradiance_volume_entries[1]), (20, irradiance_volume_entries[1]),
)); ));
} }
// Tonemapping // Tonemapping
let tonemapping_lut_entries = get_lut_bind_group_layout_entries(); let tonemapping_lut_entries = get_lut_bind_group_layout_entries();
entries = entries.extend_with_indices(( entries = entries.extend_with_indices((
(20, tonemapping_lut_entries[0]), (21, tonemapping_lut_entries[0]),
(21, tonemapping_lut_entries[1]), (22, tonemapping_lut_entries[1]),
)); ));
// Prepass // Prepass
@ -325,7 +326,7 @@ fn layout_entries(
{ {
for (entry, binding) in prepass::get_bind_group_layout_entries(layout_key) for (entry, binding) in prepass::get_bind_group_layout_entries(layout_key)
.iter() .iter()
.zip([22, 23, 24, 25]) .zip([23, 24, 25, 26])
{ {
if let Some(entry) = entry { if let Some(entry) = entry {
entries = entries.extend_with_indices(((binding as u32, *entry),)); entries = entries.extend_with_indices(((binding as u32, *entry),));
@ -336,10 +337,10 @@ fn layout_entries(
// View Transmission Texture // View Transmission Texture
entries = entries.extend_with_indices(( entries = entries.extend_with_indices((
( (
26, 27,
texture_2d(TextureSampleType::Float { filterable: true }), texture_2d(TextureSampleType::Float { filterable: true }),
), ),
(27, sampler(SamplerBindingType::Filtering)), (28, sampler(SamplerBindingType::Filtering)),
)); ));
entries.to_vec() entries.to_vec()
@ -450,7 +451,7 @@ pub fn prepare_mesh_view_bind_groups(
light_meta: Res<LightMeta>, light_meta: Res<LightMeta>,
global_light_meta: Res<GlobalClusterableObjectMeta>, global_light_meta: Res<GlobalClusterableObjectMeta>,
fog_meta: Res<FogMeta>, fog_meta: Res<FogMeta>,
view_uniforms: Res<ViewUniforms>, (view_uniforms, environment_map_uniform): (Res<ViewUniforms>, Res<EnvironmentMapUniformBuffer>),
views: Query<( views: Query<(
Entity, Entity,
&ViewShadowBindings, &ViewShadowBindings,
@ -484,6 +485,7 @@ pub fn prepare_mesh_view_bind_groups(
Some(light_probes_binding), Some(light_probes_binding),
Some(visibility_ranges_buffer), Some(visibility_ranges_buffer),
Some(ssr_binding), Some(ssr_binding),
Some(environment_map_binding),
) = ( ) = (
view_uniforms.uniforms.binding(), view_uniforms.uniforms.binding(),
light_meta.view_gpu_lights.binding(), light_meta.view_gpu_lights.binding(),
@ -493,6 +495,7 @@ pub fn prepare_mesh_view_bind_groups(
light_probes_buffer.binding(), light_probes_buffer.binding(),
visibility_ranges.buffer().buffer(), visibility_ranges.buffer().buffer(),
ssr_buffer.binding(), ssr_buffer.binding(),
environment_map_uniform.binding(),
) { ) {
for ( for (
entity, entity,
@ -559,6 +562,7 @@ pub fn prepare_mesh_view_bind_groups(
(15, diffuse_texture_view), (15, diffuse_texture_view),
(16, specular_texture_view), (16, specular_texture_view),
(17, sampler), (17, sampler),
(18, environment_map_binding.clone()),
)); ));
} }
RenderViewEnvironmentMapBindGroupEntries::Multiple { RenderViewEnvironmentMapBindGroupEntries::Multiple {
@ -570,6 +574,7 @@ pub fn prepare_mesh_view_bind_groups(
(15, diffuse_texture_views.as_slice()), (15, diffuse_texture_views.as_slice()),
(16, specular_texture_views.as_slice()), (16, specular_texture_views.as_slice()),
(17, sampler), (17, sampler),
(18, environment_map_binding.clone()),
)); ));
} }
} }
@ -590,21 +595,21 @@ pub fn prepare_mesh_view_bind_groups(
texture_view, texture_view,
sampler, sampler,
}) => { }) => {
entries = entries.extend_with_indices(((18, texture_view), (19, sampler))); entries = entries.extend_with_indices(((19, texture_view), (20, sampler)));
} }
Some(RenderViewIrradianceVolumeBindGroupEntries::Multiple { Some(RenderViewIrradianceVolumeBindGroupEntries::Multiple {
ref texture_views, ref texture_views,
sampler, sampler,
}) => { }) => {
entries = entries entries = entries
.extend_with_indices(((18, texture_views.as_slice()), (19, sampler))); .extend_with_indices(((19, texture_views.as_slice()), (20, sampler)));
} }
None => {} None => {}
} }
let lut_bindings = let lut_bindings =
get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image); 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 // When using WebGL, we can't have a depth texture with multisampling
let prepass_bindings; let prepass_bindings;
@ -614,7 +619,7 @@ pub fn prepare_mesh_view_bind_groups(
for (binding, index) in prepass_bindings for (binding, index) in prepass_bindings
.iter() .iter()
.map(Option::as_ref) .map(Option::as_ref)
.zip([22, 23, 24, 25]) .zip([23, 24, 25, 26])
.flat_map(|(b, i)| b.map(|b| (b, i))) .flat_map(|(b, i)| b.map(|b| (b, i)))
{ {
entries = entries.extend_with_indices(((index, binding),)); entries = entries.extend_with_indices(((index, binding),));
@ -630,7 +635,7 @@ pub fn prepare_mesh_view_bind_groups(
.unwrap_or(&fallback_image_zero.sampler); .unwrap_or(&fallback_image_zero.sampler);
entries = 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 { commands.entity(entity).insert(MeshViewBindGroup {
value: render_device.create_bind_group("mesh_view_bind_group", layout, &entries), value: render_device.create_bind_group("mesh_view_bind_group", layout, &entries),

View file

@ -53,47 +53,48 @@ const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: u32 = 64u;
@group(0) @binding(16) var specular_environment_map: texture_cube<f32>; @group(0) @binding(16) var specular_environment_map: texture_cube<f32>;
#endif #endif
@group(0) @binding(17) var environment_map_sampler: sampler; @group(0) @binding(17) var environment_map_sampler: sampler;
@group(0) @binding(18) var<uniform> environment_map_uniform: types::EnvironmentMapUniform;
#ifdef IRRADIANCE_VOLUMES_ARE_USABLE #ifdef IRRADIANCE_VOLUMES_ARE_USABLE
#ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY #ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY
@group(0) @binding(18) var irradiance_volumes: binding_array<texture_3d<f32>, 8u>; @group(0) @binding(19) var irradiance_volumes: binding_array<texture_3d<f32>, 8u>;
#else #else
@group(0) @binding(18) var irradiance_volume: texture_3d<f32>; @group(0) @binding(19) var irradiance_volume: texture_3d<f32>;
#endif #endif
@group(0) @binding(19) var irradiance_volume_sampler: sampler; @group(0) @binding(20) var irradiance_volume_sampler: sampler;
#endif #endif
@group(0) @binding(20) var dt_lut_texture: texture_3d<f32>; @group(0) @binding(21) var dt_lut_texture: texture_3d<f32>;
@group(0) @binding(21) var dt_lut_sampler: sampler; @group(0) @binding(22) var dt_lut_sampler: sampler;
#ifdef MULTISAMPLED #ifdef MULTISAMPLED
#ifdef DEPTH_PREPASS #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 #endif // DEPTH_PREPASS
#ifdef NORMAL_PREPASS #ifdef NORMAL_PREPASS
@group(0) @binding(23) var normal_prepass_texture: texture_multisampled_2d<f32>; @group(0) @binding(24) var normal_prepass_texture: texture_multisampled_2d<f32>;
#endif // NORMAL_PREPASS #endif // NORMAL_PREPASS
#ifdef MOTION_VECTOR_PREPASS #ifdef MOTION_VECTOR_PREPASS
@group(0) @binding(24) var motion_vector_prepass_texture: texture_multisampled_2d<f32>; @group(0) @binding(25) var motion_vector_prepass_texture: texture_multisampled_2d<f32>;
#endif // MOTION_VECTOR_PREPASS #endif // MOTION_VECTOR_PREPASS
#else // MULTISAMPLED #else // MULTISAMPLED
#ifdef DEPTH_PREPASS #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 #endif // DEPTH_PREPASS
#ifdef NORMAL_PREPASS #ifdef NORMAL_PREPASS
@group(0) @binding(23) var normal_prepass_texture: texture_2d<f32>; @group(0) @binding(24) var normal_prepass_texture: texture_2d<f32>;
#endif // NORMAL_PREPASS #endif // NORMAL_PREPASS
#ifdef MOTION_VECTOR_PREPASS #ifdef MOTION_VECTOR_PREPASS
@group(0) @binding(24) var motion_vector_prepass_texture: texture_2d<f32>; @group(0) @binding(25) var motion_vector_prepass_texture: texture_2d<f32>;
#endif // MOTION_VECTOR_PREPASS #endif // MOTION_VECTOR_PREPASS
#endif // MULTISAMPLED #endif // MULTISAMPLED
#ifdef DEFERRED_PREPASS #ifdef DEFERRED_PREPASS
@group(0) @binding(25) var deferred_prepass_texture: texture_2d<u32>; @group(0) @binding(26) var deferred_prepass_texture: texture_2d<u32>;
#endif // DEFERRED_PREPASS #endif // DEFERRED_PREPASS
@group(0) @binding(26) var view_transmission_texture: texture_2d<f32>; @group(0) @binding(27) var view_transmission_texture: texture_2d<f32>;
@group(0) @binding(27) var view_transmission_sampler: sampler; @group(0) @binding(28) var view_transmission_sampler: sampler;

View file

@ -148,3 +148,8 @@ struct ScreenSpaceReflectionsSettings {
bisection_steps: u32, bisection_steps: u32,
use_secant: u32, use_secant: u32,
}; };
struct EnvironmentMapUniform {
// Transformation matrix for the environment cubemaps in world space.
transform: mat4x4<f32>,
};

View file

@ -43,7 +43,8 @@ use bevy_utils::{info_once, prelude::default};
use crate::{ use crate::{
binding_arrays_are_usable, graph::NodePbr, prelude::EnvironmentMapLight, binding_arrays_are_usable, graph::NodePbr, prelude::EnvironmentMapLight,
MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, RenderViewLightProbes, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, RenderViewLightProbes,
ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset,
ViewLightsUniformOffset,
}; };
const SSR_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(10438925299917978850); const SSR_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(10438925299917978850);
@ -258,6 +259,7 @@ impl ViewNode for ScreenSpaceReflectionsNode {
Read<ViewFogUniformOffset>, Read<ViewFogUniformOffset>,
Read<ViewLightProbesUniformOffset>, Read<ViewLightProbesUniformOffset>,
Read<ViewScreenSpaceReflectionsUniformOffset>, Read<ViewScreenSpaceReflectionsUniformOffset>,
Read<ViewEnvironmentMapUniformOffset>,
Read<MeshViewBindGroup>, Read<MeshViewBindGroup>,
Read<ScreenSpaceReflectionsPipelineId>, Read<ScreenSpaceReflectionsPipelineId>,
); );
@ -273,6 +275,7 @@ impl ViewNode for ScreenSpaceReflectionsNode {
view_fog_offset, view_fog_offset,
view_light_probes_offset, view_light_probes_offset,
view_ssr_offset, view_ssr_offset,
view_environment_map_offset,
view_bind_group, view_bind_group,
ssr_pipeline_id, ssr_pipeline_id,
): QueryItem<'w, Self::ViewQuery>, ): QueryItem<'w, Self::ViewQuery>,
@ -324,6 +327,7 @@ impl ViewNode for ScreenSpaceReflectionsNode {
view_fog_offset.offset, view_fog_offset.offset,
**view_light_probes_offset, **view_light_probes_offset,
**view_ssr_offset, **view_ssr_offset,
**view_environment_map_offset,
], ],
); );

View file

@ -46,8 +46,9 @@ use bitflags::bitflags;
use crate::{ use crate::{
FogVolume, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, FogVolume, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup,
ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset,
ViewScreenSpaceReflectionsUniformOffset, VolumetricFogSettings, VolumetricLight, ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset, VolumetricFogSettings,
VolumetricLight,
}; };
bitflags! { bitflags! {
@ -306,6 +307,7 @@ impl ViewNode for VolumetricFogNode {
Read<ViewVolumetricFog>, Read<ViewVolumetricFog>,
Read<MeshViewBindGroup>, Read<MeshViewBindGroup>,
Read<ViewScreenSpaceReflectionsUniformOffset>, Read<ViewScreenSpaceReflectionsUniformOffset>,
Read<ViewEnvironmentMapUniformOffset>,
); );
fn run<'w>( fn run<'w>(
@ -323,6 +325,7 @@ impl ViewNode for VolumetricFogNode {
view_fog_volumes, view_fog_volumes,
view_bind_group, view_bind_group,
view_ssr_offset, view_ssr_offset,
view_environment_map_offset,
): QueryItem<'w, Self::ViewQuery>, ): QueryItem<'w, Self::ViewQuery>,
world: &'w World, world: &'w World,
) -> Result<(), NodeRunError> { ) -> Result<(), NodeRunError> {
@ -445,6 +448,7 @@ impl ViewNode for VolumetricFogNode {
view_fog_offset.offset, view_fog_offset.offset,
**view_light_probes_offset, **view_light_probes_offset,
**view_ssr_offset, **view_ssr_offset,
**view_environment_map_offset,
], ],
); );
render_pass.set_bind_group( render_pass.set_bind_group(

View file

@ -26,6 +26,7 @@ fn setup(
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 2_000.0, intensity: 2_000.0,
..default()
}, },
)); ));

View file

@ -240,12 +240,13 @@ fn add_skybox_and_environment_map(
.insert(Skybox { .insert(Skybox {
brightness: 5000.0, brightness: 5000.0,
image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
..Default::default() ..default()
}) })
.insert(EnvironmentMapLight { .insert(EnvironmentMapLight {
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 2500.0, intensity: 2500.0,
..default()
}); });
} }

View file

@ -324,6 +324,7 @@ fn setup(
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 150.0, intensity: 150.0,
..default()
}, },
FogSettings { FogSettings {
color: Color::srgba_u8(43, 44, 47, 255), color: Color::srgba_u8(43, 44, 47, 255),

View file

@ -54,7 +54,7 @@ fn setup(
Skybox { Skybox {
image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
brightness: bevy::pbr::light_consts::lux::DIRECT_SUNLIGHT, brightness: bevy::pbr::light_consts::lux::DIRECT_SUNLIGHT,
..Default::default() ..default()
}, },
)); ));

View file

@ -224,12 +224,13 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
.insert(Skybox { .insert(Skybox {
brightness: 5000.0, brightness: 5000.0,
image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
..Default::default() ..default()
}) })
.insert(EnvironmentMapLight { .insert(EnvironmentMapLight {
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 2000.0, intensity: 2000.0,
..default()
}); });
} }

View file

@ -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"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 2000.0, intensity: 2000.0,
..default()
}, },
)); ));
} }

View file

@ -56,6 +56,7 @@ fn setup(
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 2000.0, intensity: 2000.0,
..default()
}, },
DepthPrepass, DepthPrepass,
MotionVectorPrepass, MotionVectorPrepass,

View file

@ -239,7 +239,7 @@ fn spawn_camera(commands: &mut Commands, assets: &ExampleAssets) {
.insert(Skybox { .insert(Skybox {
image: assets.skybox.clone(), image: assets.skybox.clone(),
brightness: 150.0, brightness: 150.0,
..Default::default() ..default()
}); });
} }

View file

@ -26,6 +26,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 250.0, intensity: 250.0,
..default()
}, },
)); ));

View file

@ -56,6 +56,7 @@ fn setup(
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 150.0, intensity: 150.0,
..default()
}, },
CameraController::default(), CameraController::default(),
)); ));

View file

@ -130,6 +130,7 @@ fn setup(
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 900.0, intensity: 900.0,
..default()
}, },
)); ));
} }

View file

@ -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"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 2000.0, intensity: 2000.0,
..default()
}, },
// Include the `ChromaticAberration` component. // Include the `ChromaticAberration` component.
ChromaticAberration::default(), ChromaticAberration::default(),

View file

@ -151,6 +151,7 @@ fn spawn_reflection_probe(commands: &mut Commands, cubemaps: &Cubemaps) {
diffuse_map: cubemaps.diffuse.clone(), diffuse_map: cubemaps.diffuse.clone(),
specular_map: cubemaps.specular_reflection_probe.clone(), specular_map: cubemaps.specular_reflection_probe.clone(),
intensity: 5000.0, intensity: 5000.0,
..default()
}, },
}); });
} }
@ -187,7 +188,7 @@ fn add_environment_map_to_camera(
.insert(Skybox { .insert(Skybox {
image: cubemaps.skybox.clone(), image: cubemaps.skybox.clone(),
brightness: 5000.0, brightness: 5000.0,
..Default::default() ..default()
}); });
} }
} }
@ -299,6 +300,7 @@ fn create_camera_environment_map_light(cubemaps: &Cubemaps) -> EnvironmentMapLig
diffuse_map: cubemaps.diffuse.clone(), diffuse_map: cubemaps.diffuse.clone(),
specular_map: cubemaps.specular_environment_map.clone(), specular_map: cubemaps.specular_environment_map.clone(),
intensity: 5000.0, intensity: 5000.0,
..default()
} }
} }

View file

@ -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<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
asset_server: Res<AssetServer>,
) {
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<Time>,
) {
let now = time.elapsed_seconds();
let rotation = Quat::from_rotation_y(0.2 * now);
for (mut skybox, mut environment_map) in environments.iter_mut() {
skybox.rotation = rotation;
environment_map.rotation = rotation;
}
}
/// Generates a sphere.
fn create_sphere_mesh(meshes: &mut Assets<Mesh>) -> Handle<Mesh> {
// We're going to use normal maps, so make sure we've generated tangents, or
// else the normal maps won't show up.
let mut sphere_mesh = Sphere::new(1.0).mesh().build();
sphere_mesh
.generate_tangents()
.expect("Failed to generate tangents");
meshes.add(sphere_mesh)
}
/// Spawn a regular object with a clearcoat layer. This looks like car paint.
fn spawn_sphere(
commands: &mut Commands,
materials: &mut Assets<StandardMaterial>,
asset_server: &AssetServer,
sphere_mesh: &Handle<Mesh>,
) {
commands.spawn(PbrBundle {
mesh: sphere_mesh.clone(),
material: materials.add(StandardMaterial {
clearcoat: 1.0,
clearcoat_perceptual_roughness: 0.3,
clearcoat_normal_texture: Some(asset_server.load_with_settings(
"textures/ScratchedGold-Normal.png",
|settings: &mut ImageLoaderSettings| settings.is_srgb = false,
)),
metallic: 0.9,
perceptual_roughness: 0.1,
base_color: GOLD.into(),
..default()
}),
transform: Transform::from_xyz(0.0, 0.0, 0.0).with_scale(Vec3::splat(1.25)),
..default()
});
}
/// Spawns a light.
fn spawn_light(commands: &mut Commands) {
commands.spawn(PointLightBundle {
point_light: PointLight {
color: WHITE.into(),
intensity: 100000.0,
..default()
},
..default()
});
}
/// Spawns a camera with associated skybox and environment map.
fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
commands
.spawn(Camera3dBundle {
camera: Camera {
hdr: true,
..default()
},
projection: Projection::Perspective(PerspectiveProjection {
fov: 27.0 / 180.0 * PI,
..default()
}),
transform: Transform::from_xyz(0.0, 0.0, 10.0),
tonemapping: AcesFitted,
..default()
})
.insert(Skybox {
brightness: 5000.0,
image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
..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()
});
}

View file

@ -81,7 +81,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
Skybox { Skybox {
image: skybox_handle.clone(), image: skybox_handle.clone(),
brightness: 1000.0, brightness: 1000.0,
..Default::default() ..default()
}, },
)); ));

View file

@ -242,11 +242,12 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 5000.0, intensity: 5000.0,
..default()
}) })
.insert(Skybox { .insert(Skybox {
image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
brightness: 5000.0, brightness: 5000.0,
..Default::default() ..default()
}) })
.insert(ScreenSpaceReflectionsBundle::default()) .insert(ScreenSpaceReflectionsBundle::default())
.insert(Fxaa::default()); .insert(Fxaa::default());

View file

@ -78,6 +78,7 @@ fn setup(
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 2000.0, intensity: 2000.0,
..default()
}, },
)); ));

View file

@ -360,6 +360,7 @@ fn setup(
intensity: 25.0, intensity: 25.0,
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
..default()
}, },
BloomSettings::default(), BloomSettings::default(),
)); ));

View file

@ -34,6 +34,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 150.0, intensity: 150.0,
..default()
}, },
)); ));

View file

@ -152,6 +152,7 @@ fn setup(
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 150.0, intensity: 150.0,
..default()
}); });
// Create the text. // Create the text.

View file

@ -52,7 +52,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.insert(Skybox { .insert(Skybox {
image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
brightness: 1000.0, brightness: 1000.0,
..Default::default() ..default()
}) })
.insert(VolumetricFogSettings { .insert(VolumetricFogSettings {
// This value is explicitly set to 0 since we have no environment map light // This value is explicitly set to 0 since we have no environment map light

View file

@ -157,6 +157,7 @@ Example | Description
[Physically Based Rendering](../examples/3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties [Physically Based Rendering](../examples/3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
[Reflection Probes](../examples/3d/reflection_probes.rs) | Demonstrates reflection probes [Reflection Probes](../examples/3d/reflection_probes.rs) | Demonstrates reflection probes
[Render to Texture](../examples/3d/render_to_texture.rs) | Shows how to render to a texture, useful for mirrors, UI, or exporting images [Render to Texture](../examples/3d/render_to_texture.rs) | Shows how to render to a texture, useful for mirrors, UI, or exporting images
[Rotate Environment Map](../examples/3d/rotate_environment_map.rs) | Demonstrates how to rotate the skybox and the environment map simultaneously
[Screen Space Ambient Occlusion](../examples/3d/ssao.rs) | A scene showcasing screen space ambient occlusion [Screen Space Ambient Occlusion](../examples/3d/ssao.rs) | A scene showcasing screen space ambient occlusion
[Screen Space Reflections](../examples/3d/ssr.rs) | Demonstrates screen space reflections with water ripples [Screen Space Reflections](../examples/3d/ssr.rs) | Demonstrates screen space reflections with water ripples
[Shadow Biases](../examples/3d/shadow_biases.rs) | Demonstrates how shadow biases affect shadows in a 3d scene [Shadow Biases](../examples/3d/shadow_biases.rs) | Demonstrates how shadow biases affect shadows in a 3d scene

View file

@ -145,6 +145,7 @@ fn setup_scene_after_load(
specular_map: asset_server specular_map: asset_server
.load("assets/environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), .load("assets/environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 150.0, intensity: 150.0,
..default()
}, },
camera_controller, camera_controller,
)); ));