From 91c467ebfcc97d6ff75a480fac8e32050a495715 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Fri, 2 Feb 2024 12:01:56 -0300 Subject: [PATCH] Gate diffuse and specular transmission behind shader defs (#11627) # Objective - Address #10338 ## Solution - When implementing specular and diffuse transmission, I inadvertently introduced a performance regression. On high-end hardware it is barely noticeable, but **for lower-end hardware it can be pretty brutal**. If I understand it correctly, this is likely due to use of masking by the GPU to implement control flow, which means that you still pay the price for the branches you don't take; - To avoid that, this PR introduces new shader defs (controlled via `StandardMaterialKey`) that conditionally include the transmission logic, that way the shader code for both types of transmission isn't even sent to the GPU if you're not using them; - This PR also renames ~~`STANDARDMATERIAL_NORMAL_MAP`~~ to `STANDARD_MATERIAL_NORMAL_MAP` for consistency with the naming convention used elsewhere in the codebase. (Drive-by fix) --- ## Changelog - Added new shader defs, set when using transmission in the `StandardMaterial`: - `STANDARD_MATERIAL_SPECULAR_TRANSMISSION`; - `STANDARD_MATERIAL_DIFFUSE_TRANSMISSION`; - `STANDARD_MATERIAL_SPECULAR_OR_DIFFUSE_TRANSMISSION`. - Fixed performance regression caused by the introduction of transmission, by gating transmission shader logic behind the newly introduced shader defs; - Renamed ~~`STANDARDMATERIAL_NORMAL_MAP`~~ to `STANDARD_MATERIAL_NORMAL_MAP` for consistency; ## Migration Guide - If you were using `#ifdef STANDARDMATERIAL_NORMAL_MAP` on your shader code, make sure to update the name to `STANDARD_MATERIAL_NORMAL_MAP`; (with an underscore between `STANDARD` and `MATERIAL`) --- assets/shaders/array_texture.wgsl | 2 +- crates/bevy_pbr/src/pbr_material.rs | 19 +- crates/bevy_pbr/src/render/pbr_fragment.wgsl | 2 +- crates/bevy_pbr/src/render/pbr_functions.wgsl | 204 +++++++++--------- crates/bevy_pbr/src/render/pbr_prepass.wgsl | 4 +- 5 files changed, 126 insertions(+), 105 deletions(-) diff --git a/assets/shaders/array_texture.wgsl b/assets/shaders/array_texture.wgsl index f67e08aa06471..7c0216f73e592 100644 --- a/assets/shaders/array_texture.wgsl +++ b/assets/shaders/array_texture.wgsl @@ -43,7 +43,7 @@ fn fragment( double_sided, is_front, #ifdef VERTEX_TANGENTS -#ifdef STANDARDMATERIAL_NORMAL_MAP +#ifdef STANDARD_MATERIAL_NORMAL_MAP mesh.world_tangent, #endif #endif diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index ef106c010e793..25cb24fc14447 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -740,6 +740,8 @@ pub struct StandardMaterialKey { cull_mode: Option, depth_bias: i32, relief_mapping: bool, + diffuse_transmission: bool, + specular_transmission: bool, } impl From<&StandardMaterial> for StandardMaterialKey { @@ -752,6 +754,8 @@ impl From<&StandardMaterial> for StandardMaterialKey { material.parallax_mapping_method, ParallaxMappingMethod::Relief { .. } ), + diffuse_transmission: material.diffuse_transmission > 0.0, + specular_transmission: material.specular_transmission > 0.0, } } } @@ -811,11 +815,24 @@ impl Material for StandardMaterial { let shader_defs = &mut fragment.shader_defs; if key.bind_group_data.normal_map { - shader_defs.push("STANDARDMATERIAL_NORMAL_MAP".into()); + shader_defs.push("STANDARD_MATERIAL_NORMAL_MAP".into()); } if key.bind_group_data.relief_mapping { shader_defs.push("RELIEF_MAPPING".into()); } + + if key.bind_group_data.diffuse_transmission { + shader_defs.push("STANDARD_MATERIAL_DIFFUSE_TRANSMISSION".into()); + } + + if key.bind_group_data.specular_transmission { + shader_defs.push("STANDARD_MATERIAL_SPECULAR_TRANSMISSION".into()); + } + + if key.bind_group_data.diffuse_transmission || key.bind_group_data.specular_transmission + { + shader_defs.push("STANDARD_MATERIAL_SPECULAR_OR_DIFFUSE_TRANSMISSION".into()); + } } descriptor.primitive.cull_mode = key.bind_group_data.cull_mode; if let Some(label) = &mut descriptor.label { diff --git a/crates/bevy_pbr/src/render/pbr_fragment.wgsl b/crates/bevy_pbr/src/render/pbr_fragment.wgsl index 169d42f0a6f81..be759d38b573e 100644 --- a/crates/bevy_pbr/src/render/pbr_fragment.wgsl +++ b/crates/bevy_pbr/src/render/pbr_fragment.wgsl @@ -190,7 +190,7 @@ fn pbr_input_from_standard_material( double_sided, is_front, #ifdef VERTEX_TANGENTS -#ifdef STANDARDMATERIAL_NORMAL_MAP +#ifdef STANDARD_MATERIAL_NORMAL_MAP in.world_tangent, #endif #endif diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index eca7463d9b178..78dbc9a82a70b 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -50,7 +50,7 @@ fn prepare_world_normal( ) -> vec3 { var output: vec3 = world_normal; #ifndef VERTEX_TANGENTS -#ifndef STANDARDMATERIAL_NORMAL_MAP +#ifndef STANDARD_MATERIAL_NORMAL_MAP // NOTE: When NOT using normal-mapping, if looking at the back face of a double-sided // material, the normal needs to be inverted. This is a branchless version of that. output = (f32(!double_sided || is_front) * 2.0 - 1.0) * output; @@ -65,7 +65,7 @@ fn apply_normal_mapping( double_sided: bool, is_front: bool, #ifdef VERTEX_TANGENTS -#ifdef STANDARDMATERIAL_NORMAL_MAP +#ifdef STANDARD_MATERIAL_NORMAL_MAP world_tangent: vec4, #endif #endif @@ -83,7 +83,7 @@ fn apply_normal_mapping( var N: vec3 = world_normal; #ifdef VERTEX_TANGENTS -#ifdef STANDARDMATERIAL_NORMAL_MAP +#ifdef STANDARD_MATERIAL_NORMAL_MAP // NOTE: The mikktspace method of normal mapping explicitly requires that these NOT be // normalized nor any Gram-Schmidt applied to ensure the vertex normal is orthogonal to the // vertex tangent! Do not change this code unless you really know what you are doing. @@ -95,7 +95,7 @@ fn apply_normal_mapping( #ifdef VERTEX_TANGENTS #ifdef VERTEX_UVS -#ifdef STANDARDMATERIAL_NORMAL_MAP +#ifdef STANDARD_MATERIAL_NORMAL_MAP // Nt is the tangent-space normal. var Nt = textureSampleBias(pbr_bindings::normal_map_texture, pbr_bindings::normal_map_sampler, uv, mip_bias).rgb; if (standard_material_flags & pbr_types::STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u { @@ -213,24 +213,24 @@ fn apply_pbr_lighting( let light_contrib = lighting::point_light(in.world_position.xyz, light_id, roughness, NdotV, in.N, in.V, R, F0, f_ab, diffuse_color); direct_light += light_contrib * shadow; - if diffuse_transmission > 0.0 { - // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated - // world position, inverted normal and view vectors, and the following simplified - // values for a fully diffuse transmitted light contribution approximation: - // - // roughness = 1.0; - // NdotV = 1.0; - // R = vec3(0.0) // doesn't really matter - // f_ab = vec2(0.1) - // F0 = vec3(0.0) - var transmitted_shadow: f32 = 1.0; - if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT) - && (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - transmitted_shadow = shadows::fetch_point_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal); - } - let light_contrib = lighting::point_light(diffuse_transmissive_lobe_world_position.xyz, light_id, 1.0, 1.0, -in.N, -in.V, vec3(0.0), vec3(0.0), vec2(0.1), diffuse_transmissive_color); - transmitted_light += light_contrib * transmitted_shadow; +#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION + // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated + // world position, inverted normal and view vectors, and the following simplified + // values for a fully diffuse transmitted light contribution approximation: + // + // roughness = 1.0; + // NdotV = 1.0; + // R = vec3(0.0) // doesn't really matter + // f_ab = vec2(0.1) + // F0 = vec3(0.0) + var transmitted_shadow: f32 = 1.0; + if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT) + && (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + transmitted_shadow = shadows::fetch_point_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal); } + let transmitted_light_contrib = lighting::point_light(diffuse_transmissive_lobe_world_position.xyz, light_id, 1.0, 1.0, -in.N, -in.V, vec3(0.0), vec3(0.0), vec2(0.1), diffuse_transmissive_color); + transmitted_light += transmitted_light_contrib * transmitted_shadow; +#endif } // Spot lights (direct) @@ -245,24 +245,24 @@ fn apply_pbr_lighting( let light_contrib = lighting::spot_light(in.world_position.xyz, light_id, roughness, NdotV, in.N, in.V, R, F0, f_ab, diffuse_color); direct_light += light_contrib * shadow; - if diffuse_transmission > 0.0 { - // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated - // world position, inverted normal and view vectors, and the following simplified - // values for a fully diffuse transmitted light contribution approximation: - // - // roughness = 1.0; - // NdotV = 1.0; - // R = vec3(0.0) // doesn't really matter - // f_ab = vec2(0.1) - // F0 = vec3(0.0) - var transmitted_shadow: f32 = 1.0; - if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT) - && (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - transmitted_shadow = shadows::fetch_spot_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal); - } - let light_contrib = lighting::spot_light(diffuse_transmissive_lobe_world_position.xyz, light_id, 1.0, 1.0, -in.N, -in.V, vec3(0.0), vec3(0.0), vec2(0.1), diffuse_transmissive_color); - transmitted_light += light_contrib * transmitted_shadow; +#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION + // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated + // world position, inverted normal and view vectors, and the following simplified + // values for a fully diffuse transmitted light contribution approximation: + // + // roughness = 1.0; + // NdotV = 1.0; + // R = vec3(0.0) // doesn't really matter + // f_ab = vec2(0.1) + // F0 = vec3(0.0) + var transmitted_shadow: f32 = 1.0; + if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT) + && (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + transmitted_shadow = shadows::fetch_spot_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal); } + let transmitted_light_contrib = lighting::spot_light(diffuse_transmissive_lobe_world_position.xyz, light_id, 1.0, 1.0, -in.N, -in.V, vec3(0.0), vec3(0.0), vec2(0.1), diffuse_transmissive_color); + transmitted_light += transmitted_light_contrib * transmitted_shadow; +#endif } // directional lights (direct) @@ -286,41 +286,41 @@ fn apply_pbr_lighting( #endif direct_light += light_contrib * shadow; - if diffuse_transmission > 0.0 { - // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated - // world position, inverted normal and view vectors, and the following simplified - // values for a fully diffuse transmitted light contribution approximation: - // - // roughness = 1.0; - // NdotV = 1.0; - // R = vec3(0.0) // doesn't really matter - // f_ab = vec2(0.1) - // F0 = vec3(0.0) - var transmitted_shadow: f32 = 1.0; - if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT) - && (view_bindings::lights.directional_lights[i].flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - transmitted_shadow = shadows::fetch_directional_shadow(i, diffuse_transmissive_lobe_world_position, -in.world_normal, view_z); - } - let light_contrib = lighting::directional_light(i, 1.0, 1.0, -in.N, -in.V, vec3(0.0), vec3(0.0), vec2(0.1), diffuse_transmissive_color); - transmitted_light += light_contrib * transmitted_shadow; - } - } - - // Ambient light (indirect) - var indirect_light = ambient::ambient_light(in.world_position, in.N, in.V, NdotV, diffuse_color, F0, perceptual_roughness, diffuse_occlusion); - - if diffuse_transmission > 0.0 { +#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated // world position, inverted normal and view vectors, and the following simplified // values for a fully diffuse transmitted light contribution approximation: // - // perceptual_roughness = 1.0; + // roughness = 1.0; // NdotV = 1.0; + // R = vec3(0.0) // doesn't really matter + // f_ab = vec2(0.1) // F0 = vec3(0.0) - // diffuse_occlusion = vec3(1.0) - transmitted_light += ambient::ambient_light(diffuse_transmissive_lobe_world_position, -in.N, -in.V, 1.0, diffuse_transmissive_color, vec3(0.0), 1.0, vec3(1.0)); + var transmitted_shadow: f32 = 1.0; + if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT) + && (view_bindings::lights.directional_lights[i].flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + transmitted_shadow = shadows::fetch_directional_shadow(i, diffuse_transmissive_lobe_world_position, -in.world_normal, view_z); + } + let transmitted_light_contrib = lighting::directional_light(i, 1.0, 1.0, -in.N, -in.V, vec3(0.0), vec3(0.0), vec2(0.1), diffuse_transmissive_color); + transmitted_light += transmitted_light_contrib * transmitted_shadow; +#endif } + // Ambient light (indirect) + var indirect_light = ambient::ambient_light(in.world_position, in.N, in.V, NdotV, diffuse_color, F0, perceptual_roughness, diffuse_occlusion); + +#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION + // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated + // world position, inverted normal and view vectors, and the following simplified + // values for a fully diffuse transmitted light contribution approximation: + // + // perceptual_roughness = 1.0; + // NdotV = 1.0; + // F0 = vec3(0.0) + // diffuse_occlusion = vec3(1.0) + transmitted_light += ambient::ambient_light(diffuse_transmissive_lobe_world_position, -in.N, -in.V, 1.0, diffuse_transmissive_color, vec3(0.0), 1.0, vec3(1.0)); +#endif + // Environment map light (indirect) #ifdef ENVIRONMENT_MAP let environment_light = environment_map::environment_map_light( @@ -339,38 +339,42 @@ fn apply_pbr_lighting( // light in the call to `specular_transmissive_light()` below var specular_transmitted_environment_light = vec3(0.0); - if diffuse_transmission > 0.0 || specular_transmission > 0.0 { - // NOTE: We use the diffuse transmissive color, inverted normal and view vectors, - // and the following simplified values for the transmitted environment light contribution - // approximation: - // - // diffuse_color = vec3(1.0) // later we use `diffuse_transmissive_color` and `specular_transmissive_color` - // NdotV = 1.0; - // R = T // see definition below - // F0 = vec3(1.0) - // diffuse_occlusion = 1.0 - // - // (This one is slightly different from the other light types above, because the environment - // map light returns both diffuse and specular components separately, and we want to use both) - - let T = -normalize( - in.V + // start with view vector at entry point - refract(in.V, -in.N, 1.0 / ior) * thickness // add refracted vector scaled by thickness, towards exit point - ); // normalize to find exit point view vector - - let transmitted_environment_light = bevy_pbr::environment_map::environment_map_light( - perceptual_roughness, - roughness, - vec3(1.0), - 1.0, - f_ab, - -in.N, - T, - vec3(1.0), - in.world_position.xyz); - transmitted_light += transmitted_environment_light.diffuse * diffuse_transmissive_color; - specular_transmitted_environment_light = transmitted_environment_light.specular * specular_transmissive_color; - } +#ifdef STANDARD_MATERIAL_SPECULAR_OR_DIFFUSE_TRANSMISSION + // NOTE: We use the diffuse transmissive color, inverted normal and view vectors, + // and the following simplified values for the transmitted environment light contribution + // approximation: + // + // diffuse_color = vec3(1.0) // later we use `diffuse_transmissive_color` and `specular_transmissive_color` + // NdotV = 1.0; + // R = T // see definition below + // F0 = vec3(1.0) + // diffuse_occlusion = 1.0 + // + // (This one is slightly different from the other light types above, because the environment + // map light returns both diffuse and specular components separately, and we want to use both) + + let T = -normalize( + in.V + // start with view vector at entry point + refract(in.V, -in.N, 1.0 / ior) * thickness // add refracted vector scaled by thickness, towards exit point + ); // normalize to find exit point view vector + + let transmitted_environment_light = bevy_pbr::environment_map::environment_map_light( + perceptual_roughness, + roughness, + vec3(1.0), + 1.0, + f_ab, + -in.N, + T, + vec3(1.0), + in.world_position.xyz); +#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION + transmitted_light += transmitted_environment_light.diffuse * diffuse_transmissive_color; +#endif +#ifdef STANDARD_MATERIAL_SPECULAR_TRANSMISSION + specular_transmitted_environment_light = transmitted_environment_light.specular * specular_transmissive_color; +#endif +#endif // STANDARD_MATERIAL_SPECULAR_OR_DIFFUSE_TRANSMISSION #else // If there's no environment map light, there's no transmitted environment // light specular component, so we can just hardcode it to zero. @@ -383,9 +387,8 @@ fn apply_pbr_lighting( let emissive_light = emissive.rgb * output_color.a; - if specular_transmission > 0.0 { - transmitted_light += transmission::specular_transmissive_light(in.world_position, in.frag_coord.xyz, view_z, in.N, in.V, F0, ior, thickness, perceptual_roughness, specular_transmissive_color, specular_transmitted_environment_light).rgb; - } +#ifdef STANDARD_MATERIAL_SPECULAR_TRANSMISSION + transmitted_light += transmission::specular_transmissive_light(in.world_position, in.frag_coord.xyz, view_z, in.N, in.V, F0, ior, thickness, perceptual_roughness, specular_transmissive_color, specular_transmitted_environment_light).rgb; if (in.material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ATTENUATION_ENABLED_BIT) != 0u { // We reuse the `atmospheric_fog()` function here, as it's fundamentally @@ -401,6 +404,7 @@ fn apply_pbr_lighting( vec3(0.0) // TODO: Pass in (pre-attenuated) scattered light contribution here ).rgb; } +#endif // Total light output_color = vec4( diff --git a/crates/bevy_pbr/src/render/pbr_prepass.wgsl b/crates/bevy_pbr/src/render/pbr_prepass.wgsl index e17826a3f4430..8be86b5af2175 100644 --- a/crates/bevy_pbr/src/render/pbr_prepass.wgsl +++ b/crates/bevy_pbr/src/render/pbr_prepass.wgsl @@ -38,9 +38,9 @@ fn fragment( double_sided, is_front, #ifdef VERTEX_TANGENTS -#ifdef STANDARDMATERIAL_NORMAL_MAP +#ifdef STANDARD_MATERIAL_NORMAL_MAP in.world_tangent, -#endif // STANDARDMATERIAL_NORMAL_MAP +#endif // STANDARD_MATERIAL_NORMAL_MAP #endif // VERTEX_TANGENTS #ifdef VERTEX_UVS in.uv,