diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 26ed132faf25f1..9ce2f8957e58dd 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -113,6 +113,7 @@ bitflags::bitflags! { const ALPHA_MODE_OPAQUE = (1 << 6); const ALPHA_MODE_MASK = (1 << 7); const ALPHA_MODE_BLEND = (1 << 8); + const TWO_COMPONENT_NORMAL_MAP = (1 << 9); const NONE = 0; const UNINITIALIZED = 0xFFFF; } @@ -235,6 +236,22 @@ impl RenderAsset for StandardMaterial { flags |= StandardMaterialFlags::UNLIT; } let has_normal_map = material.normal_map_texture.is_some(); + if has_normal_map { + match gpu_images + .get(material.normal_map_texture.as_ref().unwrap()) + .unwrap() + .texture_format + { + // All 2-component unorm formats + TextureFormat::Rg8Unorm + | TextureFormat::Rg16Unorm + | TextureFormat::Bc5RgUnorm + | TextureFormat::EacRg11Unorm => { + flags |= StandardMaterialFlags::TWO_COMPONENT_NORMAL_MAP + } + _ => {} + } + } // NOTE: 0.5 is from the glTF default - do we want this? let mut alpha_cutoff = 0.5; match material.alpha_mode { diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 3e8965aa28ee1a..ab0f34ac59cd8f 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -329,6 +329,7 @@ impl FromWorld for MeshPipeline { GpuImage { texture, texture_view, + texture_format: image.texture_descriptor.format, sampler, size: Size::new( image.texture_descriptor.size.width as f32, diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index abf9dfaae7cbbf..b5362cd5045eb6 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -58,6 +58,7 @@ let STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 32u; 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; [[group(1), binding(0)]] var material: StandardMaterial; @@ -513,7 +514,16 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { #ifdef VERTEX_TANGENTS #ifdef STANDARDMATERIAL_NORMAL_MAP let TBN = mat3x3(T, B, N); - N = TBN * normalize(textureSample(normal_map_texture, normal_map_sampler, in.uv).rgb * 2.0 - 1.0); + // Nt is the tangent-space normal. + var Nt: vec3; + if ((material.flags & STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u) { + // Only use the xy components and derive z for 2-component normal maps. + Nt = vec3(textureSample(normal_map_texture, normal_map_sampler, in.uv).rg * 2.0 - 1.0, 0.0); + Nt.z = sqrt(1.0 - Nt.x * Nt.x - Nt.y * Nt.y); + } else { + Nt = textureSample(normal_map_texture, normal_map_sampler, in.uv).rgb * 2.0 - 1.0; + } + N = TBN * Nt; #endif #endif diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index 6f148885bb7467..0f4ba1526827cf 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -530,6 +530,7 @@ impl TextureFormatPixelInfo for TextureFormat { pub struct GpuImage { pub texture: Texture, pub texture_view: TextureView, + pub texture_format: TextureFormat, pub sampler: Sampler, pub size: Size, } @@ -594,6 +595,7 @@ impl RenderAsset for Image { Ok(GpuImage { texture, texture_view, + texture_format: image.texture_descriptor.format, sampler, size, }) diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 7468990b47ea80..f15a31d79d38d3 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -194,6 +194,7 @@ impl FromWorld for Mesh2dPipeline { GpuImage { texture, texture_view, + texture_format: image.texture_descriptor.format, sampler, size: Size::new( image.texture_descriptor.size.width as f32,