From 939957825a13ec9a0d888b53c0aae6d85120277b Mon Sep 17 00:00:00 2001 From: William Rose Date: Sat, 22 Mar 2025 11:45:20 +0000 Subject: [PATCH 1/4] Add a function for converting `PbrInput` to `LightingInput` --- crates/bevy_pbr/src/render/pbr_lighting.wgsl | 77 ++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index 4497b567e9..522354e145 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -3,6 +3,7 @@ #import bevy_pbr::{ mesh_view_types::POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE, mesh_view_bindings as view_bindings, + pbr_types::PbrInput, } #import bevy_render::maths::PI @@ -250,6 +251,82 @@ fn specular_multiscatter( return Fr; } +/// Constructs the `LightingInput` from a given `PbrInput`. +fn pbr_input_to_lighting_input(pbr_input: PbrInput) -> LightingInput { + let N = pbr_input.N; + let V = pbr_input.V; + let material = pbr_input.material; + + var lighting_input: LightingInput; + + lighting_input.layers[LAYER_BASE].N = N; + + // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" + lighting_input.layers[LAYER_BASE].R = reflect(-V, N); + let NdotV = max(dot(N, V), 0.0001); + lighting_input.layers[LAYER_BASE].NdotV = NdotV; + + lighting_input.layers[LAYER_BASE].perceptual_roughness = material.perceptual_roughness; + // calculate non-linear roughness from linear perceptualRoughness + lighting_input.layers[LAYER_BASE].roughness = + perceptualRoughnessToRoughness(material.perceptual_roughness); + + lighting_input.P = pbr_input.world_position.xyz; + lighting_input.V = V; + + lighting_input.diffuse_color = calculate_diffuse_color( + material.base_color.rgb, + material.metallic, + material.specular_transmission, + material.diffuse_transmission + ); + + lighting_input.F0_ = calculate_F0(material.base_color.rgb, material.metallic, material.reflectance); + lighting_input.F_ab = F_AB(material.perceptual_roughness, NdotV); + +#ifdef STANDARD_MATERIAL_CLEARCOAT + // Do the above calculations again for the clearcoat layer. Remember that + // the clearcoat can have its own roughness and its own normal. + let clearcoat_N = pbr_input.clearcoat_N; + + lighting_input.layers[LAYER_CLEARCOAT].N = clearcoat_N; + + lighting_input.layers[LAYER_CLEARCOAT].R = reflect(-V, clearcoat_N); + lighting_input.layers[LAYER_CLEARCOAT].NdotV = max(dot(clearcoat_N, V), 0.0001); + + lighting_input.layers[LAYER_CLEARCOAT].perceptual_roughness = material.clearcoat_perceptual_roughness; + lighting_input.layers[LAYER_CLEARCOAT].roughness = + perceptualRoughnessToRoughness(material.clearcoat_perceptual_roughness); + + lighting_input.clearcoat_strength = material.clearcoat; +#endif // STANDARD_MATERIAL_CLEARCOAT + +#ifdef STANDARD_MATERIAL_ANISOTROPY + lighting_input.anisotropy = pbr_input.anisotropy_strength; + lighting_input.Ta = pbr_input.anisotropy_T; + lighting_input.Ba = pbr_input.anisotropy_B; +#endif // STANDARD_MATERIAL_ANISOTROPY + + return lighting_input; +} + +// Diffuse strength is inversely related to metallicity, specular and diffuse transmission +fn calculate_diffuse_color( + base_color: vec3, + metallic: f32, + specular_transmission: f32, + diffuse_transmission: f32 +) -> vec3 { + return base_color * (1.0 - metallic) * (1.0 - specular_transmission) * + (1.0 - diffuse_transmission); +} + +// Remapping [0,1] reflectance to F0 +// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping +fn calculate_F0(base_color: vec3, metallic: f32, reflectance: vec3) -> vec3 { + return 0.16 * reflectance * reflectance * (1.0 - metallic) + base_color * metallic; +} + // Specular BRDF // https://google.github.io/filament/Filament.html#materialsystem/specularbrdf From dbe6479bb0ccd5b9367f825644f4c95b474e0173 Mon Sep 17 00:00:00 2001 From: William Rose Date: Sat, 22 Mar 2025 11:46:20 +0000 Subject: [PATCH 2/4] Convert SSR to use the new `pbr_input_to_lighting_input` function --- crates/bevy_pbr/src/ssr/ssr.wgsl | 75 +++++--------------------------- 1 file changed, 11 insertions(+), 64 deletions(-) diff --git a/crates/bevy_pbr/src/ssr/ssr.wgsl b/crates/bevy_pbr/src/ssr/ssr.wgsl index 3dddfa1ba3..d60d06b9e4 100644 --- a/crates/bevy_pbr/src/ssr/ssr.wgsl +++ b/crates/bevy_pbr/src/ssr/ssr.wgsl @@ -10,7 +10,6 @@ mesh_view_bindings::{view, depth_prepass_texture, deferred_prepass_texture, ssr_settings}, pbr_deferred_functions::pbr_input_from_deferred_gbuffer, pbr_deferred_types, - pbr_functions, prepass_utils, raymarch::{ depth_ray_march_from_cs, @@ -99,19 +98,19 @@ fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { return fragment; } - // Unpack the PBR input. - var specular_occlusion = pbr_input.specular_occlusion; - let world_position = pbr_input.world_position.xyz; - let N = pbr_input.N; - let V = pbr_input.V; - - // Calculate the reflection vector. - let R = reflect(-V, N); +#ifdef ENVIRONMENT_MAP + var lighting_input = lighting::pbr_input_to_lighting_input(pbr_input); + let R = lighting_input.layers[LAYER_BASE].R; +#else // ENVIRONMENT_MAP + let R = reflect(-pbr_input.V, pbr_input.N); +#endif // ENVIRONMENT_MAP // Do the raymarching. + let world_position = pbr_input.world_position.xyz; let ssr_specular = evaluate_ssr(R, world_position); var indirect_light = ssr_specular.rgb; - specular_occlusion *= ssr_specular.a; + + let specular_occlusion = pbr_input.specular_occlusion * ssr_specular.a; // Sample the environment map if necessary. // @@ -120,58 +119,6 @@ fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { // // TODO: Merge this with the duplicated code in `apply_pbr_lighting`. #ifdef ENVIRONMENT_MAP - // Unpack values required for environment mapping. - let base_color = pbr_input.material.base_color.rgb; - let metallic = pbr_input.material.metallic; - let reflectance = pbr_input.material.reflectance; - let specular_transmission = pbr_input.material.specular_transmission; - let diffuse_transmission = pbr_input.material.diffuse_transmission; - let diffuse_occlusion = pbr_input.diffuse_occlusion; - -#ifdef STANDARD_MATERIAL_CLEARCOAT - // Do the above calculations again for the clearcoat layer. Remember that - // the clearcoat can have its own roughness and its own normal. - let clearcoat = pbr_input.material.clearcoat; - let clearcoat_perceptual_roughness = pbr_input.material.clearcoat_perceptual_roughness; - let clearcoat_roughness = lighting::perceptualRoughnessToRoughness(clearcoat_perceptual_roughness); - let clearcoat_N = pbr_input.clearcoat_N; - let clearcoat_NdotV = max(dot(clearcoat_N, pbr_input.V), 0.0001); - let clearcoat_R = reflect(-pbr_input.V, clearcoat_N); -#endif // STANDARD_MATERIAL_CLEARCOAT - - // Calculate various other values needed for environment mapping. - let roughness = lighting::perceptualRoughnessToRoughness(perceptual_roughness); - let diffuse_color = pbr_functions::calculate_diffuse_color( - base_color, - metallic, - specular_transmission, - diffuse_transmission - ); - let NdotV = max(dot(N, V), 0.0001); - let F_ab = lighting::F_AB(perceptual_roughness, NdotV); - let F0 = pbr_functions::calculate_F0(base_color, metallic, reflectance); - - // Pack all the values into a structure. - var lighting_input: lighting::LightingInput; - lighting_input.layers[LAYER_BASE].NdotV = NdotV; - lighting_input.layers[LAYER_BASE].N = N; - lighting_input.layers[LAYER_BASE].R = R; - lighting_input.layers[LAYER_BASE].perceptual_roughness = perceptual_roughness; - lighting_input.layers[LAYER_BASE].roughness = roughness; - lighting_input.P = world_position.xyz; - lighting_input.V = V; - lighting_input.diffuse_color = diffuse_color; - lighting_input.F0_ = F0; - lighting_input.F_ab = F_ab; -#ifdef STANDARD_MATERIAL_CLEARCOAT - lighting_input.layers[LAYER_CLEARCOAT].NdotV = clearcoat_NdotV; - lighting_input.layers[LAYER_CLEARCOAT].N = clearcoat_N; - lighting_input.layers[LAYER_CLEARCOAT].R = clearcoat_R; - lighting_input.layers[LAYER_CLEARCOAT].perceptual_roughness = clearcoat_perceptual_roughness; - lighting_input.layers[LAYER_CLEARCOAT].roughness = clearcoat_roughness; - lighting_input.clearcoat_strength = clearcoat; -#endif // STANDARD_MATERIAL_CLEARCOAT - // Determine which cluster we're in. We'll need this to find the right // reflection probe. let cluster_index = clustered_forward::fragment_cluster_index( @@ -185,9 +132,9 @@ fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { // Accumulate the environment map light. indirect_light += view.exposure * - (environment_light.diffuse * diffuse_occlusion + + (environment_light.diffuse * pbr_input.diffuse_occlusion + environment_light.specular * specular_occlusion); -#endif +#endif // ENVIRONMENT_MAP // Write the results. return vec4(fragment.rgb + indirect_light, 1.0); From 1111a6aa5ab7975c0aa4e837acd46a6be5e9d48b Mon Sep 17 00:00:00 2001 From: William Rose Date: Sat, 22 Mar 2025 11:48:06 +0000 Subject: [PATCH 3/4] Convert `apply_pbr_lighting` to use the new `pbr_input_to_lighting_input` function --- crates/bevy_pbr/src/render/pbr_functions.wgsl | 58 +++---------------- 1 file changed, 7 insertions(+), 51 deletions(-) diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index e9b4e1f1a8..b2f878cb97 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -288,10 +288,8 @@ fn apply_pbr_lighting( // calculate non-linear roughness from linear perceptualRoughness let metallic = in.material.metallic; let perceptual_roughness = in.material.perceptual_roughness; - let roughness = lighting::perceptualRoughnessToRoughness(perceptual_roughness); let ior = in.material.ior; let thickness = in.material.thickness; - let reflectance = in.material.reflectance; let diffuse_transmission = in.material.diffuse_transmission; let specular_transmission = in.material.specular_transmission; @@ -300,67 +298,25 @@ fn apply_pbr_lighting( let diffuse_occlusion = in.diffuse_occlusion; let specular_occlusion = in.specular_occlusion; - // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" - let NdotV = max(dot(in.N, in.V), 0.0001); - let R = reflect(-in.V, in.N); - -#ifdef STANDARD_MATERIAL_CLEARCOAT - // Do the above calculations again for the clearcoat layer. Remember that - // the clearcoat can have its own roughness and its own normal. - let clearcoat = in.material.clearcoat; - let clearcoat_perceptual_roughness = in.material.clearcoat_perceptual_roughness; - let clearcoat_roughness = lighting::perceptualRoughnessToRoughness(clearcoat_perceptual_roughness); - let clearcoat_N = in.clearcoat_N; - let clearcoat_NdotV = max(dot(clearcoat_N, in.V), 0.0001); - let clearcoat_R = reflect(-in.V, clearcoat_N); -#endif // STANDARD_MATERIAL_CLEARCOAT - - let diffuse_color = calculate_diffuse_color( - output_color.rgb, - metallic, - specular_transmission, - diffuse_transmission - ); - // Diffuse transmissive strength is inversely related to metallicity and specular transmission, but directly related to diffuse transmission let diffuse_transmissive_color = output_color.rgb * (1.0 - metallic) * (1.0 - specular_transmission) * diffuse_transmission; // Calculate the world position of the second Lambertian lobe used for diffuse transmission, by subtracting material thickness let diffuse_transmissive_lobe_world_position = in.world_position - vec4(in.world_normal, 0.0) * thickness; - let F0 = calculate_F0(output_color.rgb, metallic, reflectance); - let F_ab = lighting::F_AB(perceptual_roughness, NdotV); - var direct_light: vec3 = vec3(0.0); // Transmitted Light (Specular and Diffuse) var transmitted_light: vec3 = vec3(0.0); - // Pack all the values into a structure. - var lighting_input: lighting::LightingInput; - lighting_input.layers[LAYER_BASE].NdotV = NdotV; - lighting_input.layers[LAYER_BASE].N = in.N; - lighting_input.layers[LAYER_BASE].R = R; - lighting_input.layers[LAYER_BASE].perceptual_roughness = perceptual_roughness; - lighting_input.layers[LAYER_BASE].roughness = roughness; - lighting_input.P = in.world_position.xyz; - lighting_input.V = in.V; - lighting_input.diffuse_color = diffuse_color; - lighting_input.F0_ = F0; - lighting_input.F_ab = F_ab; + var lighting_input = lighting::pbr_input_to_lighting_input(in); + let diffuse_color = lighting_input.diffuse_color; + let F0 = lighting_input.F0_; + + let NdotV = lighting_input.layers[LAYER_BASE].NdotV; #ifdef STANDARD_MATERIAL_CLEARCOAT - lighting_input.layers[LAYER_CLEARCOAT].NdotV = clearcoat_NdotV; - lighting_input.layers[LAYER_CLEARCOAT].N = clearcoat_N; - lighting_input.layers[LAYER_CLEARCOAT].R = clearcoat_R; - lighting_input.layers[LAYER_CLEARCOAT].perceptual_roughness = clearcoat_perceptual_roughness; - lighting_input.layers[LAYER_CLEARCOAT].roughness = clearcoat_roughness; - lighting_input.clearcoat_strength = clearcoat; -#endif // STANDARD_MATERIAL_CLEARCOAT -#ifdef STANDARD_MATERIAL_ANISOTROPY - lighting_input.anisotropy = in.anisotropy_strength; - lighting_input.Ta = in.anisotropy_T; - lighting_input.Ba = in.anisotropy_B; -#endif // STANDARD_MATERIAL_ANISOTROPY + let clearcoat_NdotV = lighting_input.layers[LAYER_CLEARCOAT].NdotV; +#endif // STANDARD_MATERIAL_CLEARCOAT // And do the same for transmissive if we need to. #ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION From c92073b29866e85336c46ef0b77518c107a4f27c Mon Sep 17 00:00:00 2001 From: William Rose Date: Sat, 22 Mar 2025 11:49:09 +0000 Subject: [PATCH 4/4] Remove `calculate_diffuse_color` and `calculate_F0` from `pbr_functions` because they're now duplicated in `pbr_lighting` --- .../src/deferred/pbr_deferred_functions.wgsl | 3 ++- crates/bevy_pbr/src/render/pbr_functions.wgsl | 17 ----------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl b/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl index e6254b1154..b3ac156d68 100644 --- a/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl +++ b/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl @@ -4,6 +4,7 @@ pbr_types::{PbrInput, pbr_input_new, STANDARD_MATERIAL_FLAGS_UNLIT_BIT}, pbr_deferred_types as deferred_types, pbr_functions, + lighting, rgb9e5, mesh_view_bindings::view, utils::{octahedral_encode, octahedral_decode}, @@ -64,7 +65,7 @@ fn deferred_gbuffer_from_pbr_input(in: PbrInput) -> vec4 { let metallic = in.material.metallic; let specular_transmission = in.material.specular_transmission; let diffuse_transmission = in.material.diffuse_transmission; - let diffuse_color = pbr_functions::calculate_diffuse_color( + let diffuse_color = lighting::calculate_diffuse_color( base_color, metallic, specular_transmission, diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index b2f878cb97..f994e87d9f 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -260,23 +260,6 @@ fn calculate_view( return V; } -// Diffuse strength is inversely related to metallicity, specular and diffuse transmission -fn calculate_diffuse_color( - base_color: vec3, - metallic: f32, - specular_transmission: f32, - diffuse_transmission: f32 -) -> vec3 { - return base_color * (1.0 - metallic) * (1.0 - specular_transmission) * - (1.0 - diffuse_transmission); -} - -// Remapping [0,1] reflectance to F0 -// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping -fn calculate_F0(base_color: vec3, metallic: f32, reflectance: vec3) -> vec3 { - return 0.16 * reflectance * reflectance * (1.0 - metallic) + base_color * metallic; -} - #ifndef PREPASS_FRAGMENT fn apply_pbr_lighting( in: pbr_types::PbrInput,