Skip to content

Commit

Permalink
Gate diffuse and specular transmission behind shader defs (bevyengine…
Browse files Browse the repository at this point in the history
…#11627)

# Objective

- Address bevyengine#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`)
  • Loading branch information
coreh authored and tjamaan committed Feb 6, 2024
1 parent aa91125 commit 9705993
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 105 deletions.
2 changes: 1 addition & 1 deletion assets/shaders/array_texture.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 18 additions & 1 deletion crates/bevy_pbr/src/pbr_material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,8 @@ pub struct StandardMaterialKey {
cull_mode: Option<Face>,
depth_bias: i32,
relief_mapping: bool,
diffuse_transmission: bool,
specular_transmission: bool,
}

impl From<&StandardMaterial> for StandardMaterialKey {
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/render/pbr_fragment.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
204 changes: 104 additions & 100 deletions crates/bevy_pbr/src/render/pbr_functions.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ fn prepare_world_normal(
) -> vec3<f32> {
var output: vec3<f32> = 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;
Expand All @@ -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<f32>,
#endif
#endif
Expand All @@ -83,7 +83,7 @@ fn apply_normal_mapping(
var N: vec3<f32> = 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.
Expand All @@ -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 {
Expand Down Expand Up @@ -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<f32>(0.0) // doesn't really matter
// f_ab = vec2<f32>(0.1)
// F0 = vec3<f32>(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<f32>(0.0), vec3<f32>(0.0), vec2<f32>(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<f32>(0.0) // doesn't really matter
// f_ab = vec2<f32>(0.1)
// F0 = vec3<f32>(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<f32>(0.0), vec3<f32>(0.0), vec2<f32>(0.1), diffuse_transmissive_color);
transmitted_light += transmitted_light_contrib * transmitted_shadow;
#endif
}

// Spot lights (direct)
Expand All @@ -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<f32>(0.0) // doesn't really matter
// f_ab = vec2<f32>(0.1)
// F0 = vec3<f32>(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<f32>(0.0), vec3<f32>(0.0), vec2<f32>(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<f32>(0.0) // doesn't really matter
// f_ab = vec2<f32>(0.1)
// F0 = vec3<f32>(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<f32>(0.0), vec3<f32>(0.0), vec2<f32>(0.1), diffuse_transmissive_color);
transmitted_light += transmitted_light_contrib * transmitted_shadow;
#endif
}

// directional lights (direct)
Expand All @@ -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<f32>(0.0) // doesn't really matter
// f_ab = vec2<f32>(0.1)
// F0 = vec3<f32>(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<f32>(0.0), vec3<f32>(0.0), vec2<f32>(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<f32>(0.0) // doesn't really matter
// f_ab = vec2<f32>(0.1)
// F0 = vec3<f32>(0.0)
// diffuse_occlusion = vec3<f32>(1.0)
transmitted_light += ambient::ambient_light(diffuse_transmissive_lobe_world_position, -in.N, -in.V, 1.0, diffuse_transmissive_color, vec3<f32>(0.0), 1.0, vec3<f32>(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<f32>(0.0), vec3<f32>(0.0), vec2<f32>(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<f32>(0.0)
// diffuse_occlusion = vec3<f32>(1.0)
transmitted_light += ambient::ambient_light(diffuse_transmissive_lobe_world_position, -in.N, -in.V, 1.0, diffuse_transmissive_color, vec3<f32>(0.0), 1.0, vec3<f32>(1.0));
#endif

// Environment map light (indirect)
#ifdef ENVIRONMENT_MAP
let environment_light = environment_map::environment_map_light(
Expand All @@ -339,38 +339,42 @@ fn apply_pbr_lighting(
// light in the call to `specular_transmissive_light()` below
var specular_transmitted_environment_light = vec3<f32>(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<f32>(1.0) // later we use `diffuse_transmissive_color` and `specular_transmissive_color`
// NdotV = 1.0;
// R = T // see definition below
// F0 = vec3<f32>(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<f32>(1.0),
1.0,
f_ab,
-in.N,
T,
vec3<f32>(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<f32>(1.0) // later we use `diffuse_transmissive_color` and `specular_transmissive_color`
// NdotV = 1.0;
// R = T // see definition below
// F0 = vec3<f32>(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<f32>(1.0),
1.0,
f_ab,
-in.N,
T,
vec3<f32>(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.
Expand All @@ -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
Expand All @@ -401,6 +404,7 @@ fn apply_pbr_lighting(
vec3<f32>(0.0) // TODO: Pass in (pre-attenuated) scattered light contribution here
).rgb;
}
#endif

// Total light
output_color = vec4<f32>(
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_pbr/src/render/pbr_prepass.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 9705993

Please sign in to comment.