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"
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"

View file

@ -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);

View file

@ -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 <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.
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::<EnvironmentMapUniform>(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)) = (

View file

@ -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;

View file

@ -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<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_internal_asset!(
@ -330,15 +355,41 @@ impl Plugin for LightProbePlugin {
render_app
.add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new())
.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::<IrradianceVolume>)
.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<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
/// to views, performing frustum culling and distance sorting in the process.
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
// uniforms with the results.
//

View file

@ -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, &[]);

View file

@ -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<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I>
Read<ViewFogUniformOffset>,
Read<ViewLightProbesUniformOffset>,
Read<ViewScreenSpaceReflectionsUniformOffset>,
Read<ViewEnvironmentMapUniformOffset>,
Read<MeshViewBindGroup>,
);
type ItemQuery = ();
@ -2112,10 +2113,15 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I>
#[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<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I>
view_fog.offset,
**view_light_probes,
**view_ssr,
**view_environment_map,
],
);

View file

@ -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<LightMeta>,
global_light_meta: Res<GlobalClusterableObjectMeta>,
fog_meta: Res<FogMeta>,
view_uniforms: Res<ViewUniforms>,
(view_uniforms, environment_map_uniform): (Res<ViewUniforms>, Res<EnvironmentMapUniformBuffer>),
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),

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>;
#endif
@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 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
@group(0) @binding(18) var irradiance_volume: texture_3d<f32>;
@group(0) @binding(19) var irradiance_volume: texture_3d<f32>;
#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<f32>;
@group(0) @binding(21) var dt_lut_sampler: sampler;
@group(0) @binding(21) var dt_lut_texture: texture_3d<f32>;
@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<f32>;
@group(0) @binding(24) var normal_prepass_texture: texture_multisampled_2d<f32>;
#endif // NORMAL_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
#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<f32>;
@group(0) @binding(24) var normal_prepass_texture: texture_2d<f32>;
#endif // NORMAL_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 // MULTISAMPLED
#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
@group(0) @binding(26) var view_transmission_texture: texture_2d<f32>;
@group(0) @binding(27) var view_transmission_sampler: sampler;
@group(0) @binding(27) var view_transmission_texture: texture_2d<f32>;
@group(0) @binding(28) var view_transmission_sampler: sampler;

View file

@ -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<f32>,
};

View file

@ -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<Shader> = Handle::weak_from_u128(10438925299917978850);
@ -258,6 +259,7 @@ impl ViewNode for ScreenSpaceReflectionsNode {
Read<ViewFogUniformOffset>,
Read<ViewLightProbesUniformOffset>,
Read<ViewScreenSpaceReflectionsUniformOffset>,
Read<ViewEnvironmentMapUniformOffset>,
Read<MeshViewBindGroup>,
Read<ScreenSpaceReflectionsPipelineId>,
);
@ -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,
],
);

View file

@ -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<ViewVolumetricFog>,
Read<MeshViewBindGroup>,
Read<ViewScreenSpaceReflectionsUniformOffset>,
Read<ViewEnvironmentMapUniformOffset>,
);
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(

View file

@ -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()
},
));

View file

@ -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()
});
}

View file

@ -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),

View file

@ -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()
},
));

View file

@ -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()
});
}

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

View file

@ -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,

View file

@ -239,7 +239,7 @@ fn spawn_camera(commands: &mut Commands, assets: &ExampleAssets) {
.insert(Skybox {
image: assets.skybox.clone(),
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"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 250.0,
..default()
},
));

View file

@ -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(),
));

View file

@ -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()
},
));
}

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

View file

@ -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()
}
}

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 {
image: skybox_handle.clone(),
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"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 5000.0,
..default()
})
.insert(Skybox {
image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
brightness: 5000.0,
..Default::default()
..default()
})
.insert(ScreenSpaceReflectionsBundle::default())
.insert(Fxaa::default());

View file

@ -78,6 +78,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()
},
));

View file

@ -360,6 +360,7 @@ fn setup(
intensity: 25.0,
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
..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"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 150.0,
..default()
},
));

View file

@ -152,6 +152,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()
});
// Create the text.

View file

@ -52,7 +52,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.insert(Skybox {
image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
brightness: 1000.0,
..Default::default()
..default()
})
.insert(VolumetricFogSettings {
// 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
[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
[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 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

View file

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