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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,13 @@ 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, Solari and atmosphere
bluenoise_texture = [
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we be enabling this feature in other features, e.g. for solari or env maps?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its not needed in solari just yet, we'll let Jasmine add the feature when she starts using it. And gen env maps dont need this either thanks to the fallback, so for now its fine i believe

"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 }
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_pbr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
File renamed without changes.
69 changes: 60 additions & 9 deletions crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,23 @@ pub mod graph {

use crate::{deferred::DeferredPbrLightingPlugin, graph::NodePbr};
use bevy_app::prelude::*;
use bevy_asset::{embedded_asset, load_embedded_asset, 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, ImageType};
use bevy_image::{Image, ImageSampler};
use bevy_render::{
alpha::AlphaMode,
camera::{sort_cameras, Projection},
extract_component::ExtractComponentPlugin,
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,
};
Expand Down Expand Up @@ -280,19 +285,42 @@ impl Plugin for PbrPlugin {
},
);

// Load the Spatio-temporal blue noise texture
embedded_asset!(app, "stbn.ktx2");
let bluenoise_texture = load_embedded_asset!(app, "stbn.ktx2");
let has_bluenoise = app
.get_sub_app(RenderApp)
.is_some_and(|render_app| render_app.world().is_resource_added::<Bluenoise>());

if !has_bluenoise {
let mut images = app.world_mut().resource_mut::<Assets<Image>>();
#[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;
};

// Extract the required data from the main world
render_app
.insert_resource(Bluenoise {
texture: bluenoise_texture,
})
.add_systems(
RenderStartup,
(
Expand Down Expand Up @@ -349,3 +377,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,
}
}
11 changes: 10 additions & 1 deletion crates/bevy_pbr/src/light_probe/environment_filter.wgsl
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -24,13 +24,22 @@ 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);
let noise_coords = pixel_coords & noise_size_mask;
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 {
Expand Down
105 changes: 97 additions & 8 deletions crates/bevy_pbr/src/light_probe/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
//! This module provides realtime filtering via [`bevy_light::GeneratedEnvironmentMapLight`].
//! For prefiltered environment maps, see [`bevy_light::EnvironmentMapLight`].
//! These components are intended to be added to a camera.
use bevy_asset::{load_embedded_asset, AssetServer, Assets};
use bevy_app::{App, Plugin, Update};
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Assets};
use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
use bevy_ecs::{
component::Component,
entity::Entity,
query::{QueryState, With, Without},
resource::Resource,
schedule::IntoScheduleConfigs,
system::{lifetimeless::Read, Commands, Query, Res, ResMut},
world::{FromWorld, World},
};
Expand All @@ -25,21 +28,22 @@ use bevy_math::{Quat, UVec2, Vec2};
use bevy_render::{
diagnostic::RecordDiagnostics,
render_asset::{RenderAssetUsages, RenderAssets},
render_graph::{Node, NodeRunError, RenderGraphContext, RenderLabel},
render_graph::{Node, NodeRunError, RenderGraphContext, RenderGraphExt, RenderLabel},
render_resource::{
binding_types::*, AddressMode, BindGroup, BindGroupEntries, BindGroupLayout,
BindGroupLayoutEntries, CachedComputePipelineId, ComputePassDescriptor,
ComputePipelineDescriptor, Extent3d, FilterMode, PipelineCache, Sampler,
ComputePipelineDescriptor, DownlevelFlags, Extent3d, FilterMode, PipelineCache, Sampler,
SamplerBindingType, SamplerDescriptor, ShaderDefVal, ShaderStages, ShaderType,
StorageTextureAccess, Texture, TextureAspect, TextureDescriptor, TextureDimension,
TextureFormat, TextureFormatFeatureFlags, TextureSampleType, TextureUsages, TextureView,
TextureViewDescriptor, TextureViewDimension, UniformBuffer,
},
renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue},
settings::WgpuFeatures,
sync_component::SyncComponentPlugin,
sync_world::RenderEntity,
texture::{CachedTexture, GpuImage, TextureCache},
Extract,
Extract, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems,
};

// Implementation: generate diffuse and specular cubemaps required by PBR
Expand All @@ -58,6 +62,7 @@ use bevy_render::{

use bevy_light::{EnvironmentMapLight, GeneratedEnvironmentMapLight};
use core::cmp::min;
use tracing::info;

use crate::Bluenoise;

Expand Down Expand Up @@ -101,6 +106,77 @@ pub struct DownsamplingConfig {
pub combine_bind_group: bool,
}

pub struct EnvironmentMapGenerationPlugin;

impl Plugin for EnvironmentMapGenerationPlugin {
fn build(&self, _: &mut App) {}
fn finish(&self, app: &mut App) {
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
let adapter = render_app.world().resource::<RenderAdapter>();
let device = render_app.world().resource::<RenderDevice>();

// Cubemap SPD requires at least 6 storage textures
let limit_support = device.limits().max_storage_textures_per_shader_stage >= 6
&& device.limits().max_compute_workgroup_storage_size != 0
&& device.limits().max_compute_workgroup_size_x != 0;

let downlevel_support = adapter
.get_downlevel_capabilities()
.flags
.contains(DownlevelFlags::COMPUTE_SHADERS);

if !limit_support || !downlevel_support {
info!("Disabling EnvironmentMapGenerationPlugin because compute is not supported on this platform. This is safe to ignore if you are not using EnvironmentMapGenerationPlugin.");
return;
}
} else {
return;
}

embedded_asset!(app, "environment_filter.wgsl");
embedded_asset!(app, "downsample.wgsl");
embedded_asset!(app, "copy.wgsl");

app.add_plugins(SyncComponentPlugin::<GeneratedEnvironmentMapLight>::default())
.add_systems(Update, generate_environment_map_light);

let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};

render_app
.add_render_graph_node::<DownsamplingNode>(Core3d, GeneratorNode::Downsampling)
.add_render_graph_node::<FilteringNode>(Core3d, GeneratorNode::Filtering)
.add_render_graph_edges(
Core3d,
(
Node3d::EndPrepasses,
GeneratorNode::Downsampling,
GeneratorNode::Filtering,
Node3d::StartMainPass,
),
)
.add_systems(
ExtractSchedule,
extract_generated_environment_map_entities.after(generate_environment_map_light),
)
.add_systems(
Render,
prepare_generated_environment_map_bind_groups
.in_set(RenderSystems::PrepareBindGroups),
)
.add_systems(
Render,
prepare_generated_environment_map_intermediate_textures
.in_set(RenderSystems::PrepareResources),
)
.add_systems(
RenderStartup,
initialize_generated_environment_map_resources,
);
}
}

// The number of storage textures required to combine the bind group
const REQUIRED_STORAGE_TEXTURES: u32 = 12;

Expand Down Expand Up @@ -289,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");
Expand Down Expand Up @@ -333,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,
});
Expand All @@ -344,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,
});
Expand Down Expand Up @@ -639,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);
Expand Down Expand Up @@ -672,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,
)),
);

Expand Down Expand Up @@ -709,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,
)),
);

Expand Down
Loading
Loading