Skip to content

Commit fc56c68

Browse files
committed
bevy_pbr: Fix incorrect and unnecessary normal-mapping code (#5766)
# Objective - Fixes #4019 - Fix lighting of double-sided materials when using a negative scale - The FlightHelmet.gltf model's hose uses a double-sided material. Loading the model with a uniform scale of -1.0, and comparing against Blender, it was identified that negating the world-space tangent, bitangent, and interpolated normal produces incorrect lighting. Discussion with Morten Mikkelsen clarified that this is both incorrect and unnecessary. ## Solution - Remove the code that negates the T, B, and N vectors (the interpolated world-space tangent, calculated world-space bitangent, and interpolated world-space normal) when seeing the back face of a double-sided material with negative scale. - Negate the world normal for a double-sided back face only when not using normal mapping ### Before, on `main`, flipping T, B, and N <img width="932" alt="Screenshot 2022-08-22 at 15 11 53" src="https://user-images.githubusercontent.com/302146/185965366-f776ff2c-cfa1-46d1-9c84-fdcb399c273c.png"> ### After, on this PR <img width="932" alt="Screenshot 2022-08-22 at 15 12 11" src="https://user-images.githubusercontent.com/302146/185965420-8be493e2-3b1a-4188-bd13-fd6b17a76fe7.png"> ### Double-sided material without normal maps https://user-images.githubusercontent.com/302146/185988113-44a384e7-0b55-4946-9b99-20f8c803ab7e.mp4 --- ## Changelog - Fixed: Lighting of normal-mapped, double-sided materials applied to models with negative scale - Fixed: Lighting and shadowing of back faces with no normal-mapping and a double-sided material ## Migration Guide `prepare_normal` from the `bevy_pbr::pbr_functions` shader import has been reworked. Before: ```rust pbr_input.world_normal = in.world_normal; pbr_input.N = prepare_normal( pbr_input.material.flags, in.world_normal, #ifdef VERTEX_TANGENTS #ifdef STANDARDMATERIAL_NORMAL_MAP in.world_tangent, #endif #endif in.uv, in.is_front, ); ``` After: ```rust pbr_input.world_normal = prepare_world_normal( in.world_normal, (material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u, in.is_front, ); pbr_input.N = apply_normal_mapping( pbr_input.material.flags, pbr_input.world_normal, #ifdef VERTEX_TANGENTS #ifdef STANDARDMATERIAL_NORMAL_MAP in.world_tangent, #endif #endif in.uv, ); ```
1 parent 30e3576 commit fc56c68

File tree

6 files changed

+31
-27
lines changed

6 files changed

+31
-27
lines changed

assets/shaders/array_texture.wgsl

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,23 @@ fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
3434

3535
pbr_input.frag_coord = in.frag_coord;
3636
pbr_input.world_position = in.world_position;
37-
pbr_input.world_normal = in.world_normal;
37+
pbr_input.world_normal = prepare_world_normal(
38+
in.world_normal,
39+
(pbr_input.material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u,
40+
in.is_front,
41+
);
3842

3943
pbr_input.is_orthographic = view.projection[3].w == 1.0;
4044

41-
pbr_input.N = prepare_normal(
45+
pbr_input.N = apply_normal_mapping(
4246
pbr_input.material.flags,
43-
in.world_normal,
47+
pbr_input.world_normal,
4448
#ifdef VERTEX_TANGENTS
4549
#ifdef STANDARDMATERIAL_NORMAL_MAP
4650
in.world_tangent,
4751
#endif
4852
#endif
4953
in.uv,
50-
in.is_front,
5154
);
5255
pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic);
5356

crates/bevy_pbr/src/render/mesh.wgsl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ fn vertex(vertex: Vertex) -> VertexOutput {
7070
}
7171

7272
struct FragmentInput {
73-
@builtin(front_facing) is_front: bool,
7473
#import bevy_pbr::mesh_vertex_output
7574
};
7675

crates/bevy_pbr/src/render/pbr.wgsl

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,17 @@ fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
6969

7070
pbr_input.frag_coord = in.frag_coord;
7171
pbr_input.world_position = in.world_position;
72-
pbr_input.world_normal = in.world_normal;
72+
pbr_input.world_normal = prepare_world_normal(
73+
in.world_normal,
74+
(material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u,
75+
in.is_front,
76+
);
7377

7478
pbr_input.is_orthographic = view.projection[3].w == 1.0;
7579

76-
pbr_input.N = prepare_normal(
80+
pbr_input.N = apply_normal_mapping(
7781
material.flags,
78-
in.world_normal,
82+
pbr_input.world_normal,
7983
#ifdef VERTEX_TANGENTS
8084
#ifdef STANDARDMATERIAL_NORMAL_MAP
8185
in.world_tangent,
@@ -84,7 +88,6 @@ fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
8488
#ifdef VERTEX_UVS
8589
in.uv,
8690
#endif
87-
in.is_front,
8891
);
8992
pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic);
9093
output_color = pbr(pbr_input);

crates/bevy_pbr/src/render/pbr_functions.wgsl

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,23 @@ fn alpha_discard(material: StandardMaterial, output_color: vec4<f32>) -> vec4<f3
2323
return color;
2424
}
2525

26-
// NOTE: This ensures that the world_normal is normalized and if
27-
// vertex tangents and normal maps then normal mapping may be applied.
28-
fn prepare_normal(
26+
fn prepare_world_normal(
27+
world_normal: vec3<f32>,
28+
double_sided: bool,
29+
is_front: bool,
30+
) -> vec3<f32> {
31+
var output: vec3<f32> = world_normal;
32+
#ifndef VERTEX_TANGENTS
33+
#ifndef STANDARDMATERIAL_NORMAL_MAP
34+
// NOTE: When NOT using normal-mapping, if looking at the back face of a double-sided
35+
// material, the normal needs to be inverted. This is a branchless version of that.
36+
output = (f32(!double_sided || is_front) * 2.0 - 1.0) * output;
37+
#endif
38+
#endif
39+
return output;
40+
}
41+
42+
fn apply_normal_mapping(
2943
standard_material_flags: u32,
3044
world_normal: vec3<f32>,
3145
#ifdef VERTEX_TANGENTS
@@ -36,7 +50,6 @@ fn prepare_normal(
3650
#ifdef VERTEX_UVS
3751
uv: vec2<f32>,
3852
#endif
39-
is_front: bool,
4053
) -> vec3<f32> {
4154
// NOTE: The mikktspace method of normal mapping explicitly requires that the world normal NOT
4255
// be re-normalized in the fragment shader. This is primarily to match the way mikktspace
@@ -57,18 +70,6 @@ fn prepare_normal(
5770
#endif
5871
#endif
5972

60-
if ((standard_material_flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u) {
61-
if (!is_front) {
62-
N = -N;
63-
#ifdef VERTEX_TANGENTS
64-
#ifdef STANDARDMATERIAL_NORMAL_MAP
65-
T = -T;
66-
B = -B;
67-
#endif
68-
#endif
69-
}
70-
}
71-
7273
#ifdef VERTEX_TANGENTS
7374
#ifdef VERTEX_UVS
7475
#ifdef STANDARDMATERIAL_NORMAL_MAP

crates/bevy_sprite/src/mesh2d/color_material.wgsl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ var texture_sampler: sampler;
1919
var<uniform> mesh: Mesh2d;
2020

2121
struct FragmentInput {
22-
@builtin(front_facing) is_front: bool,
2322
#import bevy_sprite::mesh2d_vertex_output
2423
};
2524

crates/bevy_sprite/src/mesh2d/mesh2d.wgsl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ fn vertex(vertex: Vertex) -> VertexOutput {
5555
}
5656

5757
struct FragmentInput {
58-
@builtin(front_facing) is_front: bool,
5958
#import bevy_sprite::mesh2d_vertex_output
6059
};
6160

0 commit comments

Comments
 (0)