diff --git a/Cargo.toml b/Cargo.toml index c7a03998db982..11a4d90f5ae5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -579,6 +579,9 @@ gltf_convert_coordinates_default = [ # Enable collecting debug information about systems and components to help with diagnostics debug = ["bevy_internal/debug"] +# Include spatio-temporal blue noise KTX2 file used by generated environment maps +bluenoise_texture = ["bevy_internal/bluenoise_texture", "ktx2", "bevy_image/zstd"] + [dependencies] bevy_internal = { path = "crates/bevy_internal", version = "0.17.0-dev", default-features = false } tracing = { version = "0.1", default-features = false, optional = true } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 5f2bddb602a1e..54b9bfb7e7bea 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -69,6 +69,8 @@ statically-linked-dxc = ["bevy_render/statically-linked-dxc"] # Include tonemapping LUT KTX2 files. tonemapping_luts = ["bevy_core_pipeline/tonemapping_luts"] +# Include Bluenoise texture for environment map generation. +bluenoise_texture = ["bevy_pbr?/bluenoise_texture"] # Include SMAA LUT KTX2 Files smaa_luts = ["bevy_anti_aliasing/smaa_luts"] diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 299c0f54c3c86..a3beade1d5cbf 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -18,6 +18,7 @@ experimental_pbr_pcss = ["bevy_light/experimental_pbr_pcss"] pbr_specular_textures = [] pbr_clustered_decals = [] pbr_light_textures = [] +bluenoise_texture = ["bevy_render/ktx2", "bevy_image/ktx2", "bevy_image/zstd"] shader_format_glsl = ["bevy_render/shader_format_glsl"] trace = ["bevy_render/trace"] # Enables the meshlet renderer for dense high-poly scenes (experimental) diff --git a/assets/textures/stbn.ktx2 b/crates/bevy_pbr/src/bluenoise/stbn.ktx2 similarity index 100% rename from assets/textures/stbn.ktx2 rename to crates/bevy_pbr/src/bluenoise/stbn.ktx2 diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index d970849d44e5c..db81fb8309a54 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -133,10 +133,12 @@ pub mod graph { use crate::{deferred::DeferredPbrLightingPlugin, graph::NodePbr}; use bevy_app::prelude::*; -use bevy_asset::{io::embedded::GetAssetServer, AssetApp, AssetPath, Assets, Handle}; +use bevy_asset::{AssetApp, AssetPath, Assets, Handle, RenderAssetUsages}; use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; use bevy_ecs::prelude::*; -use bevy_image::Image; +#[cfg(feature = "bluenoise_texture")] +use bevy_image::{CompressedImageFormats, ImageSampler, ImageType}; +use bevy_image::{Image, ImageSampler}; use bevy_render::{ alpha::AlphaMode, camera::{sort_cameras, Projection}, @@ -144,7 +146,10 @@ use bevy_render::{ extract_resource::ExtractResourcePlugin, load_shader_library, render_graph::RenderGraph, - render_resource::ShaderRef, + render_resource::{ + Extent3d, ShaderRef, TextureDataOrder, TextureDescriptor, TextureDimension, TextureFormat, + TextureUsages, + }, sync_component::SyncComponentPlugin, ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems, }; @@ -186,20 +191,12 @@ impl Default for PbrPlugin { } /// A resource that stores the spatio-temporal blue noise texture. -#[derive(Resource, Clone)] +#[derive(Resource)] pub struct Bluenoise { /// Texture handle for spatio-temporal blue noise pub texture: Handle, } -impl FromWorld for Bluenoise { - fn from_world(world: &mut World) -> Self { - Self { - texture: world.get_asset_server().load("textures/stbn.ktx2"), - } - } -} - impl Plugin for PbrPlugin { fn build(&self, app: &mut App) { load_shader_library!(app, "render/pbr_types.wgsl"); @@ -288,6 +285,38 @@ impl Plugin for PbrPlugin { }, ); + let has_bluenoise = if let Some(render_app) = app.get_sub_app(RenderApp) { + render_app.world().is_resource_added::() + } else { + true + }; + + if !has_bluenoise { + let mut images = app.world_mut().resource_mut::>(); + #[cfg(feature = "bluenoise_texture")] + let handle = { + let image = Image::from_buffer( + include_bytes!("bluenoise/stbn.ktx2"), + ImageType::Extension("ktx2"), + CompressedImageFormats::NONE, + false, + ImageSampler::Default, + RenderAssetUsages::RENDER_WORLD, + ) + .expect("Failed to decode embedded blue-noise texture"); + images.add(image) + }; + + #[cfg(not(feature = "bluenoise_texture"))] + let handle = { images.add(stbn_placeholder()) }; + + if let Some(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .world_mut() + .insert_resource(Bluenoise { texture: handle }); + } + } + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; @@ -350,3 +379,26 @@ impl Plugin for PbrPlugin { app.insert_resource(global_cluster_settings); } } + +pub fn stbn_placeholder() -> Image { + let format = TextureFormat::Rgba8Unorm; + let data = vec![255, 0, 255, 255]; + Image { + data: Some(data), + data_order: TextureDataOrder::default(), + texture_descriptor: TextureDescriptor { + size: Extent3d::default(), + format, + dimension: TextureDimension::D2, + label: None, + mip_level_count: 1, + sample_count: 1, + usage: TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + sampler: ImageSampler::Default, + texture_view_descriptor: None, + asset_usage: RenderAssetUsages::RENDER_WORLD, + copy_on_resize: false, + } +} diff --git a/crates/bevy_pbr/src/light_probe/environment_filter.wgsl b/crates/bevy_pbr/src/light_probe/environment_filter.wgsl index 390a66537f319..7b24b7604cd5a 100644 --- a/crates/bevy_pbr/src/light_probe/environment_filter.wgsl +++ b/crates/bevy_pbr/src/light_probe/environment_filter.wgsl @@ -1,7 +1,7 @@ #import bevy_render::maths::PI #import bevy_pbr::{ lighting, - utils::{sample_cosine_hemisphere, dir_to_cube_uv, sample_cube_dir, hammersley_2d} + utils::{sample_cosine_hemisphere, dir_to_cube_uv, sample_cube_dir, hammersley_2d, rand_vec2f} } struct FilteringConstants { @@ -24,6 +24,7 @@ fn sample_environment(dir: vec3f, level: f32) -> vec4f { } // Blue noise randomization +#ifdef HAS_BLUE_NOISE fn sample_noise(pixel_coords: vec2u) -> vec4f { let noise_size = vec2u(1) << constants.noise_size_bits; let noise_size_mask = noise_size - vec2u(1u); @@ -31,6 +32,14 @@ fn sample_noise(pixel_coords: vec2u) -> vec4f { let uv = vec2f(noise_coords) / vec2f(noise_size); return textureSampleLevel(blue_noise_texture, input_sampler, uv, 0u, 0.0); } +#else +// pseudo-random numbers using RNG +fn sample_noise(pixel_coords: vec2u) -> vec4f { + var rng_state: u32 = (pixel_coords.x * 3966231743u) ^ (pixel_coords.y * 3928936651u); + let rnd = rand_vec2f(&rng_state); + return vec4f(rnd, 0.0, 0.0); +} +#endif // Calculate LOD for environment map lookup using filtered importance sampling fn calculate_environment_map_lod(pdf: f32, width: f32, samples: f32) -> f32 { diff --git a/crates/bevy_pbr/src/light_probe/generate.rs b/crates/bevy_pbr/src/light_probe/generate.rs index 31ae0a7153690..2f3f0a491bbf8 100644 --- a/crates/bevy_pbr/src/light_probe/generate.rs +++ b/crates/bevy_pbr/src/light_probe/generate.rs @@ -145,7 +145,6 @@ impl Plugin for EnvironmentMapGenerationPlugin { }; render_app - .init_resource::() .add_render_graph_node::(Core3d, GeneratorNode::Downsampling) .add_render_graph_node::(Core3d, GeneratorNode::Filtering) .add_render_graph_edges( @@ -366,6 +365,10 @@ pub fn initialize_generated_environment_map_resources( if combine_bind_group { shader_defs.push(ShaderDefVal::Int("COMBINE_BIND_GROUP".into(), 1)); } + #[cfg(feature = "bluenoise_texture")] + { + shader_defs.push(ShaderDefVal::Int("HAS_BLUE_NOISE".into(), 1)); + } let downsampling_shader = load_embedded_asset!(asset_server.as_ref(), "downsample.wgsl"); let env_filter_shader = load_embedded_asset!(asset_server.as_ref(), "environment_filter.wgsl"); @@ -410,7 +413,7 @@ pub fn initialize_generated_environment_map_resources( layout: vec![layouts.radiance.clone()], push_constant_ranges: vec![], shader: env_filter_shader.clone(), - shader_defs: vec![], + shader_defs: shader_defs.clone(), entry_point: Some("generate_radiance_map".into()), zero_initialize_workgroup_memory: false, }); @@ -421,7 +424,7 @@ pub fn initialize_generated_environment_map_resources( layout: vec![layouts.irradiance.clone()], push_constant_ranges: vec![], shader: env_filter_shader, - shader_defs: vec![], + shader_defs: shader_defs.clone(), entry_point: Some("generate_irradiance_map".into()), zero_initialize_workgroup_memory: false, }); @@ -716,6 +719,15 @@ pub fn prepare_generated_environment_map_bind_groups( (first, second) }; + // create a 2d array view of the bluenoise texture + let stbn_texture_view = stbn_texture + .texture + .clone() + .create_view(&TextureViewDescriptor { + dimension: Some(TextureViewDimension::D2Array), + ..Default::default() + }); + // Create radiance map bind groups for each mip level let num_mips = mip_count as usize; let mut radiance_bind_groups = Vec::with_capacity(num_mips); @@ -749,7 +761,7 @@ pub fn prepare_generated_environment_map_bind_groups( &samplers.linear, &mip_storage_view, &radiance_constants_buffer, - &stbn_texture.texture_view, + &stbn_texture_view, )), ); @@ -786,7 +798,7 @@ pub fn prepare_generated_environment_map_bind_groups( &samplers.linear, &irradiance_map, &irradiance_constants_buffer, - &stbn_texture.texture_view, + &stbn_texture_view, )), );