diff --git a/Cargo.toml b/Cargo.toml index 66be632d7ad42..d5a5b33e14867 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -572,9 +572,6 @@ smaa_luts = ["bevy_internal/smaa_luts"] # Include spatio-temporal blue noise KTX2 file used by generated environment maps, Solari and atmosphere bluenoise_texture = ["bevy_internal/bluenoise_texture"] -# Include a preintegrated BRDF Look Up Table for more accurate specular shading. -dfg_lut = ["bevy_internal/dfg_lut"] - # NVIDIA Deep Learning Super Sampling dlss = ["bevy_internal/dlss"] diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 36943d996b107..2c679515f4337 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -82,9 +82,6 @@ smaa_luts = ["bevy_anti_alias?/smaa_luts", "ktx2", "bevy_image/zstd"] # Include Bluenoise texture for environment map generation. bluenoise_texture = ["bevy_pbr?/bluenoise_texture", "ktx2", "bevy_image/zstd"] -# Include a preintegrated BRDF Look Up Table for more accurate specular shading. -dfg_lut = ["bevy_pbr?/dfg_lut", "ktx2", "bevy_image/zstd"] - # NVIDIA Deep Learning Super Sampling dlss = ["bevy_anti_alias/dlss", "bevy_solari?/dlss"] diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index e370e93d38dbb..1fd56bf7ddaaa 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -21,7 +21,6 @@ experimental_pbr_pcss = ["bevy_light/experimental_pbr_pcss"] pbr_clustered_decals = [] pbr_light_textures = [] bluenoise_texture = ["bevy_image/ktx2", "bevy_image/zstd"] -dfg_lut = ["bevy_image/ktx2", "bevy_image/zstd"] shader_format_glsl = ["bevy_shader/shader_format_glsl"] trace = ["bevy_render/trace"] # Enables the meshlet renderer for dense high-poly scenes (experimental) @@ -47,7 +46,10 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } bevy_gltf = { path = "../bevy_gltf", version = "0.19.0-dev", optional = true } bevy_light = { path = "../bevy_light", version = "0.19.0-dev" } bevy_log = { path = "../bevy_log", version = "0.19.0-dev" } -bevy_image = { path = "../bevy_image", version = "0.19.0-dev" } +bevy_image = { path = "../bevy_image", version = "0.19.0-dev", features = [ + "ktx2", + "zstd", +] } bevy_mesh = { path = "../bevy_mesh", version = "0.19.0-dev", features = [ "morph", "bevy_mikktspace", diff --git a/crates/bevy_pbr/src/environment_map/dfg.ktx2 b/crates/bevy_pbr/src/environment_map/dfg.ktx2 deleted file mode 100644 index faa32d5b3ad10..0000000000000 Binary files a/crates/bevy_pbr/src/environment_map/dfg.ktx2 and /dev/null differ diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index c54210e84bb75..e05890ccb1c94 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -164,23 +164,21 @@ pub struct Bluenoise { pub texture: Handle, } -/// LTC (Linearly Transformed Cosines) LUT textures for area light shading. +/// A texture array with 2 LTC LUT and 1 DFG LUT +/// +/// Index 0 and 1: LTC (Linearly Transformed Cosines) LUT textures for area light shading. /// /// `ltc_1` encodes the 4 non-trivial elements of the inverse GGX LTC matrix. /// `ltc_2` encodes amplitude and Fresnel-related weights. /// /// [LUT source and fitting code](https://github.com/selfshadow/ltc_code/blob/master/fit/results) +/// +/// Index 2: The split-sum approximation LUT (`F_AB`) indexed by (`NdotV`, `perceptual_roughness`). +/// +// See https://github.com/bevyengine/bevy/pull/23737 for information on how the DFG LUT was generated. #[derive(Resource, Clone)] -pub struct LtcLuts { - pub ltc_1: Handle, - pub ltc_2: Handle, -} - -// See https://github.com/bevyengine/bevy/pull/23737 for information on how the LUT was generated. -/// The split-sum approximation LUT (`F_AB`) indexed by (`NdotV`, `perceptual_roughness`). -#[derive(Resource, Clone)] -pub struct DfgLut { - pub texture: Handle, +pub struct LtcDfgLuts { + pub image: Handle, } impl Plugin for PbrPlugin { @@ -279,7 +277,7 @@ impl Plugin for PbrPlugin { let mut images = app.world_mut().resource_mut::>(); #[cfg(feature = "bluenoise_texture")] let handle = { - let image = Image::from_buffer( + let mut image = Image::from_buffer( include_bytes!("bluenoise/stbn.ktx2"), ImageType::Extension("ktx2"), CompressedImageFormats::NONE, @@ -288,6 +286,7 @@ impl Plugin for PbrPlugin { RenderAssetUsages::RENDER_WORLD, ) .expect("Failed to decode embedded blue-noise texture"); + image.texture_descriptor.label = Some("bluenoise_texture"); images.add(image) }; @@ -301,64 +300,30 @@ impl Plugin for PbrPlugin { } } - let has_ltc_luts = app + let has_ltc_dfg_luts = app .get_sub_app(RenderApp) - .is_some_and(|render_app| render_app.world().is_resource_added::()); + .is_some_and(|render_app| render_app.world().is_resource_added::()); - if !has_ltc_luts { + if !has_ltc_dfg_luts { let mut images = app.world_mut().resource_mut::>(); - let ltc_luts = LtcLuts { - ltc_1: images.add( - Image::from_buffer( - include_bytes!("ltc/ltc1.ktx2"), - ImageType::Extension("ktx2"), - CompressedImageFormats::NONE, - false, - ImageSampler::linear(), - RenderAssetUsages::RENDER_WORLD, - ) - .expect("Failed to decode embedded LTC LUT 1"), - ), - ltc_2: images.add( - Image::from_buffer( - include_bytes!("ltc/ltc2.ktx2"), + let ltc_dfg_luts = LtcDfgLuts { + image: images.add({ + let mut img = Image::from_buffer( + include_bytes!("ltc_dfg/ltc_dfg.ktx2"), ImageType::Extension("ktx2"), CompressedImageFormats::NONE, false, ImageSampler::linear(), RenderAssetUsages::RENDER_WORLD, ) - .expect("Failed to decode embedded LTC LUT 2"), - ), + .expect("Failed to decode embedded LTC DFG LUT"); + img.texture_descriptor.label = Some("ltc_dfg_luts"); + img + }), }; if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.world_mut().insert_resource(ltc_luts); - } - } - - let has_dfg_lut = app - .get_sub_app(RenderApp) - .is_some_and(|render_app| render_app.world().is_resource_added::()); - - if !has_dfg_lut { - #[cfg(feature = "dfg_lut")] - let texture = app.world_mut().resource_mut::>().add( - Image::from_buffer( - include_bytes!("environment_map/dfg.ktx2"), - ImageType::Extension("ktx2"), - CompressedImageFormats::NONE, - false, - ImageSampler::linear(), - RenderAssetUsages::RENDER_WORLD, - ) - .expect("Failed to decode embedded DFG LUT"), - ); - #[cfg(not(feature = "dfg_lut"))] - let texture = Handle::default(); - - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.world_mut().insert_resource(DfgLut { texture }); + render_app.world_mut().insert_resource(ltc_dfg_luts); } } diff --git a/crates/bevy_pbr/src/ltc/ltc1.ktx2 b/crates/bevy_pbr/src/ltc/ltc1.ktx2 deleted file mode 100644 index 81553fe0c1e6f..0000000000000 Binary files a/crates/bevy_pbr/src/ltc/ltc1.ktx2 and /dev/null differ diff --git a/crates/bevy_pbr/src/ltc/ltc2.ktx2 b/crates/bevy_pbr/src/ltc/ltc2.ktx2 deleted file mode 100644 index 616a6d0c47b48..0000000000000 Binary files a/crates/bevy_pbr/src/ltc/ltc2.ktx2 and /dev/null differ diff --git a/crates/bevy_pbr/src/ltc_dfg/ltc_dfg.ktx2 b/crates/bevy_pbr/src/ltc_dfg/ltc_dfg.ktx2 new file mode 100644 index 0000000000000..ed49286a8c945 Binary files /dev/null and b/crates/bevy_pbr/src/ltc_dfg/ltc_dfg.ktx2 differ diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 6b90cffa7149c..6dab64ee67039 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -3337,9 +3337,6 @@ impl SpecializedMeshPipeline for MeshPipeline { if cfg!(feature = "bluenoise_texture") { shader_defs.push("BLUE_NOISE_TEXTURE".into()); } - if cfg!(feature = "dfg_lut") { - shader_defs.push("DFG_LUT".into()); - } let bind_group_layout = self.get_view_layout(key.into()); let mut bind_group_layout = vec![ diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 16a3eda5f24f0..2cad9595060c6 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -1,4 +1,4 @@ -use crate::{DfgLut, LtcLuts}; +use crate::LtcDfgLuts; use alloc::sync::Arc; use bevy_core_pipeline::{ oit::{resolve::is_oit_supported, OitBuffers, OrderIndependentTransparencySettings}, @@ -436,28 +436,14 @@ pub fn layout_entries( texture_2d_array(TextureSampleType::Float { filterable: false }), ),)); } - // LTC LUTs for area lights + // LTC LUTs for area lights and DFG LUT entries = entries.extend_with_indices(( ( 36, - texture_2d(TextureSampleType::Float { filterable: true }), - ), - ( - 37, - texture_2d(TextureSampleType::Float { filterable: true }), + texture_2d_array(TextureSampleType::Float { filterable: true }), ), - (38, sampler(SamplerBindingType::Filtering)), + (37, sampler(SamplerBindingType::Filtering)), )); - // DFG LUT - if cfg!(feature = "dfg_lut") { - entries = entries.extend_with_indices(( - ( - 39, - texture_2d(TextureSampleType::Float { filterable: true }), - ), - (40, sampler(SamplerBindingType::Filtering)), - )); - } let mut binding_array_entries = DynamicBindGroupLayoutEntries::new(ShaderStages::FRAGMENT); binding_array_entries = binding_array_entries.extend_with_indices(( (0, environment_map_entries[0]), @@ -662,16 +648,14 @@ pub fn prepare_mesh_view_bind_groups( atmosphere_buffer, atmosphere_sampler, blue_noise, - ltc_luts, - dfg_lut, + ltc_dfg_luts, ): ( Res, Res, Option>, Option>, Res, - Res, - Res, + Res, ), ) { if let ( @@ -843,26 +827,15 @@ pub fn prepare_mesh_view_bind_groups( entries = entries.extend_with_indices(((35, stbn_view),)); } - // LTC LUTs for area lights - let (ltc1_view, ltc_sampler) = images - .get(<c_luts.ltc_1) + // LTC LUTs for area lights and DFG LUT. + let (ltc_dfg_view, ltc_dfg_sampler) = images + .get(<c_dfg_luts.image) .map(|img| (&img.texture_view, &img.sampler)) - .unwrap_or((&fallback_image.d2.texture_view, &fallback_image.d2.sampler)); - let ltc2_view = images - .get(<c_luts.ltc_2) - .map(|img| &img.texture_view) - .unwrap_or(&fallback_image.d2.texture_view); - entries = - entries.extend_with_indices(((36, ltc1_view), (37, ltc2_view), (38, ltc_sampler))); - - // DFG LUT - if cfg!(feature = "dfg_lut") { - let (dfg_view, dfg_sampler) = images - .get(&dfg_lut.texture) - .map(|img| (&img.texture_view, &img.sampler)) - .unwrap_or((&fallback_image.d2.texture_view, &fallback_image.d2.sampler)); - entries = entries.extend_with_indices(((39, dfg_view), (40, dfg_sampler))); - } + .unwrap_or(( + &fallback_image.d2_array.texture_view, + &fallback_image.d2_array.sampler, + )); + entries = entries.extend_with_indices(((36, ltc_dfg_view), (37, ltc_dfg_sampler))); let mut entries_binding_array = DynamicBindGroupEntries::new(); diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index 9311ab0d9a70d..2985678f5eb04 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -107,13 +107,8 @@ const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: u32 = 64u; @group(0) @binding(35) var blue_noise_texture: texture_2d_array; #endif // BLUE_NOISE_TEXTURE -@group(0) @binding(36) var ltc_lut1: texture_2d; -@group(0) @binding(37) var ltc_lut2: texture_2d; -@group(0) @binding(38) var ltc_lut_sampler: sampler; -#ifdef DFG_LUT -@group(0) @binding(39) var dfg_lut: texture_2d; -@group(0) @binding(40) var dfg_lut_sampler: sampler; -#endif // DFG_LUT +@group(0) @binding(36) var ltc_dfg_lut: texture_2d_array; +@group(0) @binding(37) var ltc_dfg_lut_sampler: sampler; #ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY @group(1) @binding(0) var diffuse_environment_maps: binding_array, 8u>; diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index 5f8b6e8f635b5..e4c016335061a 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -527,18 +527,7 @@ fn Fd_Burley( // Scale/bias approximation fn F_AB(perceptual_roughness: f32, NdotV: f32) -> vec2 { -#ifdef DFG_LUT - return textureSampleLevel(view_bindings::dfg_lut, view_bindings::dfg_lut_sampler, vec2(NdotV, perceptual_roughness), 0.0).rg; -#else - // Polynomial approximation, see https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile - let c0 = vec4(-1.0, -0.0275, -0.572, 0.022); - let c1 = vec4(1.0, 0.0425, 1.04, -0.04); - let r = perceptual_roughness * c0 + c1; - let a004 = min(r.x * r.x, exp2(-9.28 * NdotV)) * r.x + r.y; - // Keep F_ab positive to avoid divide-by-zero in downstream BRDF terms. - let f_ab_epsilon = 0.00005; - return max(vec2(-1.04, 1.04) * a004 + r.zw, vec2(f_ab_epsilon)); -#endif + return textureSampleLevel(view_bindings::ltc_dfg_lut, view_bindings::ltc_dfg_lut_sampler, vec2(NdotV, perceptual_roughness), 2, 0.0).rg; } fn EnvBRDFApprox(F0: vec3, F_ab: vec2) -> vec3 { @@ -1046,8 +1035,8 @@ fn rect_light( let LUT_SCALE = 63.0 / 64.0; let LUT_BIAS = 0.5 / 64.0; let uv = vec2(perceptual_roughness, sqrt(1.0 - NdotV)) * LUT_SCALE + LUT_BIAS; - let t1 = textureSampleLevel(view_bindings::ltc_lut1, view_bindings::ltc_lut_sampler, uv, 0.0); - let t2 = textureSampleLevel(view_bindings::ltc_lut2, view_bindings::ltc_lut_sampler, uv, 0.0); + let t1 = textureSampleLevel(view_bindings::ltc_dfg_lut, view_bindings::ltc_dfg_lut_sampler, uv, 0, 0.0); + let t2 = textureSampleLevel(view_bindings::ltc_dfg_lut, view_bindings::ltc_dfg_lut_sampler, uv, 1, 0.0); // Reconstruct the GGX inverse-LTC matrix let Minv = mat3x3( @@ -1077,8 +1066,8 @@ fn rect_light( // Sample LUTs for clearcoat layer let cc_uv = vec2(clearcoat_perceptual_roughness, sqrt(1.0 - clearcoat_NdotV)) * LUT_SCALE + LUT_BIAS; - let tc1 = textureSampleLevel(view_bindings::ltc_lut1, view_bindings::ltc_lut_sampler, cc_uv, 0.0); - let tc2 = textureSampleLevel(view_bindings::ltc_lut2, view_bindings::ltc_lut_sampler, cc_uv, 0.0); + let tc1 = textureSampleLevel(view_bindings::ltc_dfg_lut, view_bindings::ltc_dfg_lut_sampler, cc_uv, 0, 0.0); + let tc2 = textureSampleLevel(view_bindings::ltc_dfg_lut, view_bindings::ltc_dfg_lut_sampler, cc_uv, 1, 0.0); let Minv_cc = mat3x3( vec3(tc1.x, 0.0, tc1.y), vec3(0.0, 1.0, 0.0), diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index b8b08e56eae68..1969bc50d96b1 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -574,9 +574,6 @@ impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline { if cfg!(feature = "bluenoise_texture") { shader_defs.push("BLUE_NOISE_TEXTURE".into()); } - if cfg!(feature = "dfg_lut") { - shader_defs.push("DFG_LUT".into()); - } #[cfg(not(target_arch = "wasm32"))] shader_defs.push("USE_DEPTH_SAMPLERS".into()); diff --git a/crates/bevy_solari/Cargo.toml b/crates/bevy_solari/Cargo.toml index 7f8c4bee22053..8f7c902662452 100644 --- a/crates/bevy_solari/Cargo.toml +++ b/crates/bevy_solari/Cargo.toml @@ -22,9 +22,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } bevy_shader = { path = "../bevy_shader", version = "0.19.0-dev" } bevy_math = { path = "../bevy_math", version = "0.19.0-dev" } bevy_mesh = { path = "../bevy_mesh", version = "0.19.0-dev" } -bevy_pbr = { path = "../bevy_pbr", version = "0.19.0-dev", features = [ - "dfg_lut", -] } +bevy_pbr = { path = "../bevy_pbr", version = "0.19.0-dev" } bevy_platform = { path = "../bevy_platform", version = "0.19.0-dev", default-features = false, features = [ "std", ] } diff --git a/crates/bevy_solari/src/scene/binder.rs b/crates/bevy_solari/src/scene/binder.rs index e65a4d2b9237e..3044c72613840 100644 --- a/crates/bevy_solari/src/scene/binder.rs +++ b/crates/bevy_solari/src/scene/binder.rs @@ -8,7 +8,8 @@ use bevy_ecs::{ }; use bevy_math::{ops::cos, Mat4, Vec3}; use bevy_pbr::{ - DfgLut, ExtractedDirectionalLight, MeshMaterial3d, PreviousGlobalTransform, StandardMaterial, + ExtractedDirectionalLight, LtcDfgLuts, MeshMaterial3d, PreviousGlobalTransform, + StandardMaterial, }; use bevy_platform::{collections::HashMap, hash::FixedHasher}; use bevy_render::{ @@ -48,7 +49,7 @@ pub fn prepare_raytracing_scene_bindings( material_assets: Res, texture_assets: Res>, fallback_texture: Res, - dfg_lut: Res, + ltc_dfg_luts: Res, render_device: Res, pipeline_cache: Res, render_queue: Res, @@ -267,9 +268,19 @@ pub fn prepare_raytracing_scene_bindings( command_encoder.build_acceleration_structures(&[], [&tlas]); render_queue.submit([command_encoder.finish()]); - let (dfg_view, dfg_sampler) = texture_assets - .get(&dfg_lut.texture) - .map(|img| (&img.texture_view, &img.sampler)) + let dfg_lut = texture_assets.get(<c_dfg_luts.image).map(|img| { + ( + img.texture.create_view(&TextureViewDescriptor { + dimension: Some(TextureViewDimension::D2), + base_array_layer: 2, + ..Default::default() + }), + &img.sampler, + ) + }); + let (dfg_view, dfg_sampler) = dfg_lut + .as_ref() + .map(|(dfg_view, sampler)| (dfg_view, *sampler)) .unwrap_or(( &fallback_texture.d2.texture_view, &fallback_texture.d2.sampler, diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 724db9e1f7de0..984da206b251f 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -120,7 +120,6 @@ This is the complete `bevy` cargo feature list, without "profiles" or "collectio |debug_glam_assert|Enable assertions in debug builds to check the validity of parameters passed to glam| |default_font|Include a default font, containing only ASCII characters, at the cost of a 20kB binary size increase| |detailed_trace|Enable detailed trace event logging. These trace events are expensive even when off, thus they require compile time opt-in| -|dfg_lut|Include a preintegrated BRDF Look Up Table for more accurate specular shading.| |dlss|NVIDIA Deep Learning Super Sampling| |dynamic_linking|Force dynamic linking, which improves iterative compile times| |embedded_watcher|Enables watching in memory asset providers for Bevy Asset hot-reloading|