From 579928e8e024adae2b4befcbcc155af2acd48e44 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Thu, 7 Apr 2022 15:50:14 +0000 Subject: [PATCH] bevy_pbr: Support flipping tangent space normal map y for DirectX normal maps (#4433) # Objective - Normal maps authored for DirectX use a left-handed convention and have their tangent space normal in the texture inverted from what we need. Support this. - Details here: https://doc.babylonjs.com/divingDeeper/materials/advanced/normalMaps ## Solution - Add a `StandardMaterial` `flip_normal_map_y` boolean field - Add a `STANDARDMATERIAL_FLIP_NORMAL_MAP_Y` flag to `StandardMaterialFlags` and in the PBR shader - Flip the y-component of the tangent space normal just after sampling it from the normal map texture ## Screenshots ### Before Screenshot 2022-04-06 at 21 04 44 ### After Screenshot 2022-04-06 at 21 03 39 --- ## Changelog - Added: Support for flipping the normal map texture y component for normal maps authored for use with DirectX --- crates/bevy_pbr/src/pbr_material.rs | 8 ++++++++ crates/bevy_pbr/src/render/pbr.wgsl | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index a9dccbdc3b960..a63950042757c 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -47,6 +47,9 @@ pub struct StandardMaterial { /// defaults to 0.5 which is mapped to 4% reflectance in the shader pub reflectance: f32, pub normal_map_texture: Option>, + /// Normal map textures authored for DirectX have their y-component flipped. Set this to flip + /// it to right-handed conventions. + pub flip_normal_map_y: bool, pub occlusion_texture: Option>, /// Support two-sided lighting by automatically flipping the normals for "back" faces /// within the PBR lighting shader. @@ -84,6 +87,7 @@ impl Default for StandardMaterial { reflectance: 0.5, occlusion_texture: None, normal_map_texture: None, + flip_normal_map_y: false, double_sided: false, cull_mode: Some(Face::Back), unlit: false, @@ -124,6 +128,7 @@ bitflags::bitflags! { const ALPHA_MODE_MASK = (1 << 7); const ALPHA_MODE_BLEND = (1 << 8); const TWO_COMPONENT_NORMAL_MAP = (1 << 9); + const FLIP_NORMAL_MAP_Y = (1 << 10); const NONE = 0; const UNINITIALIZED = 0xFFFF; } @@ -262,6 +267,9 @@ impl RenderAsset for StandardMaterial { } _ => {} } + if material.flip_normal_map_y { + flags |= StandardMaterialFlags::FLIP_NORMAL_MAP_Y; + } } // NOTE: 0.5 is from the glTF default - do we want this? let mut alpha_cutoff = 0.5; diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index 1e1e69f02a235..9a7854ce32cfc 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -59,6 +59,7 @@ let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 64u; let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 128u; let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 256u; let STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP: u32 = 512u; +let STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y: u32 = 1024u; [[group(1), binding(0)]] var material: StandardMaterial; @@ -525,6 +526,10 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { } else { Nt = textureSample(normal_map_texture, normal_map_sampler, in.uv).rgb * 2.0 - 1.0; } + // Normal maps authored for DirectX require flipping the y component + if ((material.flags & STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y) != 0u) { + Nt.y = -Nt.y; + } N = normalize(TBN * Nt); #endif #endif