diff --git a/pipelined/bevy_gltf2/src/loader.rs b/pipelined/bevy_gltf2/src/loader.rs index ea75f6c56dd24b..ea2dbb0b183f6b 100644 --- a/pipelined/bevy_gltf2/src/loader.rs +++ b/pipelined/bevy_gltf2/src/loader.rs @@ -128,12 +128,12 @@ async fn load_gltf<'a, 'b>( mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute); } - // if let Some(vertex_attribute) = reader - // .read_tangents() - // .map(|v| VertexAttributeValues::Float32x4(v.collect())) - // { - // mesh.set_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute); - // } + if let Some(vertex_attribute) = reader + .read_tangents() + .map(|v| VertexAttributeValues::Float32x4(v.collect())) + { + mesh.set_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute); + } if let Some(vertex_attribute) = reader .read_tex_coords(0) @@ -382,15 +382,16 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle< None }; - // let normal_map: Option> = if let Some(normal_texture) = material.normal_texture() { - // // TODO: handle normal_texture.scale - // // TODO: handle normal_texture.tex_coord() (the *set* index for the right texcoords) - // let label = texture_label(&normal_texture.texture()); - // let path = AssetPath::new_ref(load_context.path(), Some(&label)); - // Some(load_context.get_handle(path)) - // } else { - // None - // }; + let normal_map_texture: Option> = + if let Some(normal_texture) = material.normal_texture() { + // TODO: handle normal_texture.scale + // TODO: handle normal_texture.tex_coord() (the *set* index for the right texcoords) + let label = texture_label(&normal_texture.texture()); + let path = AssetPath::new_ref(load_context.path(), Some(&label)); + Some(load_context.get_handle(path)) + } else { + None + }; let metallic_roughness_texture = if let Some(info) = pbr.metallic_roughness_texture() { // TODO: handle info.tex_coord() (the *set* index for the right texcoords) @@ -430,7 +431,7 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle< perceptual_roughness: pbr.roughness_factor(), metallic: pbr.metallic_factor(), metallic_roughness_texture, - // normal_map, + normal_map_texture, double_sided: material.double_sided(), occlusion_texture, emissive: Color::rgba(emissive[0], emissive[1], emissive[2], 1.0), diff --git a/pipelined/bevy_pbr2/src/lib.rs b/pipelined/bevy_pbr2/src/lib.rs index a134de8c326f7c..23dfb56fa57bbf 100644 --- a/pipelined/bevy_pbr2/src/lib.rs +++ b/pipelined/bevy_pbr2/src/lib.rs @@ -70,7 +70,8 @@ impl Plugin for PbrPlugin { .init_resource::() .init_resource::>() .init_resource::() - .init_resource::>(); + .init_resource::>() + .init_resource::>(); let draw_shadow_mesh = DrawShadowMesh::new(&mut render_app.world); let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); diff --git a/pipelined/bevy_pbr2/src/material.rs b/pipelined/bevy_pbr2/src/material.rs index 869f96fe966c7e..72f050402fd51f 100644 --- a/pipelined/bevy_pbr2/src/material.rs +++ b/pipelined/bevy_pbr2/src/material.rs @@ -1,4 +1,4 @@ -use crate::PbrPipeline; +use crate::{PbrPipeline, StandardMaterialFlags}; use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, Handle}; use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; @@ -16,21 +16,6 @@ use bevy_render2::{ use crevice::std140::{AsStd140, Std140}; use wgpu::{BindGroupDescriptor, BindGroupEntry, BindingResource}; -// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.frag! -bitflags::bitflags! { - #[repr(transparent)] - struct StandardMaterialFlags: u32 { - const BASE_COLOR_TEXTURE = (1 << 0); - const EMISSIVE_TEXTURE = (1 << 1); - const METALLIC_ROUGHNESS_TEXTURE = (1 << 2); - const OCCLUSION_TEXTURE = (1 << 3); - const DOUBLE_SIDED = (1 << 4); - const UNLIT = (1 << 5); - const NONE = 0; - const UNINITIALIZED = 0xFFFF; - } -} - /// A material with "standard" properties used in PBR lighting /// Standard property values with pictures here https://google.github.io/filament/Material%20Properties.pdf #[derive(Debug, Clone, TypeUuid)] @@ -58,6 +43,7 @@ pub struct StandardMaterial { /// Specular intensity for non-metals on a linear scale of [0.0, 1.0] /// defaults to 0.5 which is mapped to 4% reflectance in the shader pub reflectance: f32, + pub normal_map_texture: Option>, pub occlusion_texture: Option>, pub double_sided: bool, pub unlit: bool, @@ -84,6 +70,7 @@ impl Default for StandardMaterial { // Expressed in a linear scale and equivalent to 4% reflectance see https://google.github.io/filament/Material%20Properties.pdf reflectance: 0.5, occlusion_texture: None, + normal_map_texture: None, double_sided: false, unlit: false, } @@ -140,6 +127,7 @@ impl Plugin for StandardMaterialPlugin { pub struct GpuStandardMaterial { pub buffer: Buffer, pub bind_group: BindGroup, + pub flags: StandardMaterialFlags, } impl RenderAsset for StandardMaterial { @@ -185,6 +173,13 @@ impl RenderAsset for StandardMaterial { } else { return Err(PrepareAssetError::RetryNextUpdate(material)); }; + let (normal_map_texture_view, normal_map_sampler) = if let Some(result) = + image_handle_to_view_sampler(pbr_pipeline, gpu_images, &material.normal_map_texture) + { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; let (occlusion_texture_view, occlusion_sampler) = if let Some(result) = image_handle_to_view_sampler(pbr_pipeline, gpu_images, &material.occlusion_texture) { @@ -211,13 +206,16 @@ impl RenderAsset for StandardMaterial { if material.unlit { flags |= StandardMaterialFlags::UNLIT; } + if material.normal_map_texture.is_some() { + flags |= StandardMaterialFlags::NORMAL_MAP_TEXTURE; + } let value = StandardMaterialUniformData { base_color: material.base_color.as_rgba_linear().into(), emissive: material.emissive.into(), roughness: material.perceptual_roughness, metallic: material.metallic, reflectance: material.reflectance, - flags: flags.bits, + flags: flags.bits(), }; let value_std140 = value.as_std140(); @@ -264,12 +262,24 @@ impl RenderAsset for StandardMaterial { binding: 8, resource: BindingResource::Sampler(occlusion_sampler), }, + BindGroupEntry { + binding: 9, + resource: BindingResource::TextureView(normal_map_texture_view), + }, + BindGroupEntry { + binding: 10, + resource: BindingResource::Sampler(normal_map_sampler), + }, ], label: Some("pbr_standard_material_bind_group"), layout: &pbr_pipeline.material_layout, }); - Ok(GpuStandardMaterial { buffer, bind_group }) + Ok(GpuStandardMaterial { + buffer, + bind_group, + flags, + }) } } diff --git a/pipelined/bevy_pbr2/src/render/light.rs b/pipelined/bevy_pbr2/src/render/light.rs index bd1a91c453bded..3871dee24fee5a 100644 --- a/pipelined/bevy_pbr2/src/render/light.rs +++ b/pipelined/bevy_pbr2/src/render/light.rs @@ -101,8 +101,8 @@ pub const DIRECTIONAL_SHADOW_LAYERS: u32 = MAX_DIRECTIONAL_LIGHTS as u32; pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float; pub struct ShadowPipeline { - pub pipeline: CachedPipelineId, pub view_layout: BindGroupLayout, + pub mesh_layout: BindGroupLayout, pub point_light_sampler: Sampler, pub directional_light_sampler: Sampler, } @@ -133,15 +133,81 @@ impl FromWorld for ShadowPipeline { }); let pbr_pipeline = world.get_resource::().unwrap(); - let descriptor = RenderPipelineDescriptor { - vertex: VertexState { - shader: SHADOW_SHADER_HANDLE.typed::(), - entry_point: "vertex".into(), - shader_defs: vec![], - buffers: vec![VertexBufferLayout { - array_stride: 32, - step_mode: VertexStepMode::Vertex, - attributes: vec![ + + ShadowPipeline { + view_layout, + mesh_layout: pbr_pipeline.mesh_layout.clone(), + point_light_sampler: render_device.create_sampler(&SamplerDescriptor { + address_mode_u: AddressMode::ClampToEdge, + address_mode_v: AddressMode::ClampToEdge, + address_mode_w: AddressMode::ClampToEdge, + mag_filter: FilterMode::Linear, + min_filter: FilterMode::Linear, + mipmap_filter: FilterMode::Nearest, + compare: Some(CompareFunction::GreaterEqual), + ..Default::default() + }), + directional_light_sampler: render_device.create_sampler(&SamplerDescriptor { + address_mode_u: AddressMode::ClampToEdge, + address_mode_v: AddressMode::ClampToEdge, + address_mode_w: AddressMode::ClampToEdge, + mag_filter: FilterMode::Linear, + min_filter: FilterMode::Linear, + mipmap_filter: FilterMode::Nearest, + compare: Some(CompareFunction::GreaterEqual), + ..Default::default() + }), + } + } +} + +bitflags::bitflags! { + #[repr(transparent)] + pub struct ShadowPipelineKey: u32 { + const NONE = 0; + const VERTEX_TANGENTS = (1 << 0); + } +} + +impl SpecializedPipeline for ShadowPipeline { + type Key = ShadowPipelineKey; + + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { + let (vertex_array_stride, vertex_attributes) = + if key.contains(ShadowPipelineKey::VERTEX_TANGENTS) { + ( + 48, + vec![ + // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 12, + shader_location: 0, + }, + // Normal + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 0, + shader_location: 1, + }, + // Uv (GOTCHA! uv is no longer third in the buffer due to how Mesh sorts attributes (alphabetically)) + VertexAttribute { + format: VertexFormat::Float32x2, + offset: 40, + shader_location: 2, + }, + // Tangent + VertexAttribute { + format: VertexFormat::Float32x4, + offset: 24, + shader_location: 3, + }, + ], + ) + } else { + ( + 32, + vec![ // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) VertexAttribute { format: VertexFormat::Float32x3, @@ -161,10 +227,21 @@ impl FromWorld for ShadowPipeline { shader_location: 2, }, ], + ) + }; + RenderPipelineDescriptor { + vertex: VertexState { + shader: SHADOW_SHADER_HANDLE.typed::(), + entry_point: "vertex".into(), + shader_defs: vec![], + buffers: vec![VertexBufferLayout { + array_stride: vertex_array_stride, + step_mode: VertexStepMode::Vertex, + attributes: vertex_attributes, }], }, fragment: None, - layout: Some(vec![view_layout.clone(), pbr_pipeline.mesh_layout.clone()]), + layout: Some(vec![self.view_layout.clone(), self.mesh_layout.clone()]), primitive: PrimitiveState { topology: PrimitiveTopology::TriangleList, strip_index_format: None, @@ -192,32 +269,6 @@ impl FromWorld for ShadowPipeline { }), multisample: MultisampleState::default(), label: Some("shadow_pipeline".into()), - }; - - let mut render_pipeline_cache = world.get_resource_mut::().unwrap(); - ShadowPipeline { - pipeline: render_pipeline_cache.queue(descriptor), - view_layout, - point_light_sampler: render_device.create_sampler(&SamplerDescriptor { - address_mode_u: AddressMode::ClampToEdge, - address_mode_v: AddressMode::ClampToEdge, - address_mode_w: AddressMode::ClampToEdge, - mag_filter: FilterMode::Linear, - min_filter: FilterMode::Linear, - mipmap_filter: FilterMode::Nearest, - compare: Some(CompareFunction::GreaterEqual), - ..Default::default() - }), - directional_light_sampler: render_device.create_sampler(&SamplerDescriptor { - address_mode_u: AddressMode::ClampToEdge, - address_mode_v: AddressMode::ClampToEdge, - address_mode_w: AddressMode::ClampToEdge, - mag_filter: FilterMode::Linear, - min_filter: FilterMode::Linear, - mipmap_filter: FilterMode::Nearest, - compare: Some(CompareFunction::GreaterEqual), - ..Default::default() - }), } } } @@ -630,7 +681,10 @@ pub fn queue_shadow_view_bind_group( pub fn queue_shadows( shadow_draw_functions: Res>, shadow_pipeline: Res, - casting_meshes: Query>, Without)>, + casting_meshes: Query<(Entity, &Handle), Without>, + render_meshes: Res>, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, mut view_lights: Query<&ViewLights>, mut view_light_shadow_phases: Query<&mut RenderPhase>, ) { @@ -643,10 +697,18 @@ pub fn queue_shadows( for view_light_entity in view_lights.lights.iter().copied() { let mut shadow_phase = view_light_shadow_phases.get_mut(view_light_entity).unwrap(); // TODO: this should only queue up meshes that are actually visible by each "light view" - for entity in casting_meshes.iter() { + for (entity, mesh_handle) in casting_meshes.iter() { + let mut key = ShadowPipelineKey::empty(); + if let Some(mesh) = render_meshes.get(mesh_handle) { + if mesh.vertex_attributes.contains(Mesh::ATTRIBUTE_TANGENT) { + key |= ShadowPipelineKey::VERTEX_TANGENTS; + } + } + let pipeline_id = pipelines.specialize(&mut pipeline_cache, &shadow_pipeline, key); + shadow_phase.add(Shadow { draw_function: draw_shadow_mesh, - pipeline: shadow_pipeline.pipeline, + pipeline: pipeline_id, entity, distance: 0.0, // TODO: sort back-to-front }) diff --git a/pipelined/bevy_pbr2/src/render/mod.rs b/pipelined/bevy_pbr2/src/render/mod.rs index 959ede3bf8724b..fe178283ca9eed 100644 --- a/pipelined/bevy_pbr2/src/render/mod.rs +++ b/pipelined/bevy_pbr2/src/render/mod.rs @@ -47,6 +47,22 @@ bitflags::bitflags! { } } +// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.wgsl! +bitflags::bitflags! { + #[repr(transparent)] + pub struct StandardMaterialFlags: u32 { + const BASE_COLOR_TEXTURE = (1 << 0); + const EMISSIVE_TEXTURE = (1 << 1); + const METALLIC_ROUGHNESS_TEXTURE = (1 << 2); + const OCCLUSION_TEXTURE = (1 << 3); + const DOUBLE_SIDED = (1 << 4); + const UNLIT = (1 << 5); + const NORMAL_MAP_TEXTURE = (1 << 6); + const NONE = 0; + const UNINITIALIZED = 0xFFFF; + } +} + pub fn extract_meshes( mut commands: Commands, mut previous_caster_len: Local, @@ -300,6 +316,27 @@ impl FromWorld for PbrPipeline { }, count: None, }, + // Normal Map Texture + BindGroupLayoutEntry { + binding: 9, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + }, + count: None, + }, + // Normal Map Texture Sampler + BindGroupLayoutEntry { + binding: 10, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler { + comparison: false, + filtering: true, + }, + count: None, + }, ], label: Some("pbr_material_layout"), }); @@ -374,8 +411,10 @@ bitflags::bitflags! { // NOTE: Apparently quadro drivers support up to 64x MSAA. /// MSAA uses the highest 6 bits for the MSAA sample count - 1 to support up to 64x MSAA. pub struct PbrPipelineKey: u32 { - const NONE = 0; - const MSAA_RESERVED_BITS = PbrPipelineKey::MSAA_MASK_BITS << PbrPipelineKey::MSAA_SHIFT_BITS; + const NONE = 0; + const VERTEX_TANGENTS = (1 << 0); + const STANDARDMATERIAL_NORMAL_MAP = (1 << 1); + const MSAA_RESERVED_BITS = PbrPipelineKey::MSAA_MASK_BITS << PbrPipelineKey::MSAA_SHIFT_BITS; } } @@ -397,15 +436,41 @@ impl SpecializedPipeline for PbrPipeline { type Key = PbrPipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - RenderPipelineDescriptor { - vertex: VertexState { - shader: PBR_SHADER_HANDLE.typed::(), - entry_point: "vertex".into(), - shader_defs: vec![], - buffers: vec![VertexBufferLayout { - array_stride: 32, - step_mode: VertexStepMode::Vertex, - attributes: vec![ + let (vertex_array_stride, vertex_attributes) = + if key.contains(PbrPipelineKey::VERTEX_TANGENTS) { + ( + 48, + vec![ + // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 12, + shader_location: 0, + }, + // Normal + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 0, + shader_location: 1, + }, + // Uv (GOTCHA! uv is no longer third in the buffer due to how Mesh sorts attributes (alphabetically)) + VertexAttribute { + format: VertexFormat::Float32x2, + offset: 40, + shader_location: 2, + }, + // Tangent + VertexAttribute { + format: VertexFormat::Float32x4, + offset: 24, + shader_location: 3, + }, + ], + ) + } else { + ( + 32, + vec![ // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) VertexAttribute { format: VertexFormat::Float32x3, @@ -425,11 +490,29 @@ impl SpecializedPipeline for PbrPipeline { shader_location: 2, }, ], + ) + }; + let mut shader_defs = Vec::new(); + if key.contains(PbrPipelineKey::VERTEX_TANGENTS) { + shader_defs.push(String::from("VERTEX_TANGENTS")); + } + if key.contains(PbrPipelineKey::STANDARDMATERIAL_NORMAL_MAP) { + shader_defs.push(String::from("STANDARDMATERIAL_NORMAL_MAP")); + } + RenderPipelineDescriptor { + vertex: VertexState { + shader: PBR_SHADER_HANDLE.typed::(), + entry_point: "vertex".into(), + shader_defs: shader_defs.clone(), + buffers: vec![VertexBufferLayout { + array_stride: vertex_array_stride, + step_mode: VertexStepMode::Vertex, + attributes: vertex_attributes, }], }, fragment: Some(FragmentState { shader: PBR_SHADER_HANDLE.typed::(), - shader_defs: vec![], + shader_defs, entry_point: "fragment".into(), targets: vec![ColorTargetState { format: TextureFormat::bevy_default(), @@ -528,11 +611,14 @@ pub fn queue_meshes( light_meta: Res, msaa: Res, view_uniforms: Res, + render_meshes: Res>, render_materials: Res>, - standard_material_meshes: Query< - (Entity, &Handle, &MeshUniform), - With>, - >, + standard_material_meshes: Query<( + Entity, + &Handle, + &Handle, + &MeshUniform, + )>, mut views: Query<( Entity, &ExtractedView, @@ -544,7 +630,6 @@ pub fn queue_meshes( view_uniforms.uniforms.binding(), light_meta.view_gpu_lights.binding(), ) { - let msaa_key = PbrPipelineKey::from_msaa_samples(msaa.samples); for (entity, view, view_lights, mut transparent_phase) in views.iter_mut() { let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor { entries: &[ @@ -595,13 +680,27 @@ pub fn queue_meshes( let view_matrix = view.transform.compute_matrix(); let view_row_2 = view_matrix.row(2); - for (entity, material_handle, mesh_uniform) in standard_material_meshes.iter() { - if !render_materials.contains_key(material_handle) { + for (entity, material_handle, mesh_handle, mesh_uniform) in + standard_material_meshes.iter() + { + let mut key = PbrPipelineKey::from_msaa_samples(msaa.samples); + if let Some(material) = render_materials.get(material_handle) { + if material + .flags + .contains(StandardMaterialFlags::NORMAL_MAP_TEXTURE) + { + key |= PbrPipelineKey::STANDARDMATERIAL_NORMAL_MAP; + } + } else { continue; } + if let Some(mesh) = render_meshes.get(mesh_handle) { + if mesh.vertex_attributes.contains(Mesh::ATTRIBUTE_TANGENT) { + key |= PbrPipelineKey::VERTEX_TANGENTS; + } + } + let pipeline_id = pipelines.specialize(&mut pipeline_cache, &pbr_pipeline, key); - let pipeline_id = - pipelines.specialize(&mut pipeline_cache, &pbr_pipeline, msaa_key); // NOTE: row 2 of the view matrix dotted with column 3 of the model matrix // gives the z component of translation of the mesh in view space let mesh_z = view_row_2.dot(mesh_uniform.transform.col(3)); diff --git a/pipelined/bevy_pbr2/src/render/pbr.wgsl b/pipelined/bevy_pbr2/src/render/pbr.wgsl index 06e39204989355..048c184e8dbbb6 100644 --- a/pipelined/bevy_pbr2/src/render/pbr.wgsl +++ b/pipelined/bevy_pbr2/src/render/pbr.wgsl @@ -26,6 +26,9 @@ struct Vertex { [[location(0)]] position: vec3; [[location(1)]] normal: vec3; [[location(2)]] uv: vec2; +#ifdef VERTEX_TANGENTS + [[location(3)]] tangent: vec4; +#endif }; struct VertexOutput { @@ -33,6 +36,9 @@ struct VertexOutput { [[location(0)]] world_position: vec4; [[location(1)]] world_normal: vec3; [[location(2)]] uv: vec2; +#ifdef VERTEX_TANGENTS + [[location(3)]] world_tangent: vec4; +#endif }; [[stage(vertex)]] @@ -48,6 +54,16 @@ fn vertex(vertex: Vertex) -> VertexOutput { mesh.inverse_transpose_model.y.xyz, mesh.inverse_transpose_model.z.xyz ) * vertex.normal; +#ifdef VERTEX_TANGENTS + out.world_tangent = vec4( + mat3x3( + mesh.model.x.xyz, + mesh.model.y.xyz, + mesh.model.z.xyz + ) * vertex.tangent.xyz, + vertex.tangent.w + ); +#endif return out; } @@ -164,6 +180,10 @@ var metallic_roughness_sampler: sampler; var occlusion_texture: texture_2d; [[group(1), binding(8)]] var occlusion_sampler: sampler; +[[group(1), binding(9)]] +var normal_map_texture: texture_2d; +[[group(1), binding(10)]] +var normal_map_sampler: sampler; let PI: f32 = 3.141592653589793; @@ -475,6 +495,9 @@ struct FragmentInput { [[location(0)]] world_position: vec4; [[location(1)]] world_normal: vec3; [[location(2)]] uv: vec2; +#ifdef VERTEX_TANGENTS + [[location(3)]] world_tangent: vec4; +#endif }; [[stage(fragment)]] @@ -510,27 +533,31 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { var N: vec3 = normalize(in.world_normal); - // FIXME: Normal maps need an additional vertex attribute and vertex stage output/fragment stage input - // Just use a separate shader for lit with normal maps? - // # ifdef STANDARDMATERIAL_NORMAL_MAP - // vec3 T = normalize(v_WorldTangent.xyz); - // vec3 B = cross(N, T) * v_WorldTangent.w; - // # endif +#ifdef VERTEX_TANGENTS +#ifdef STANDARDMATERIAL_NORMAL_MAP + var T: vec3 = normalize(in.world_tangent.xyz); + var B: vec3 = cross(N, T) * in.world_tangent.w; +#endif +#endif if ((material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u) { if (!in.is_front) { N = -N; +#ifdef VERTEX_TANGENTS +#ifdef STANDARDMATERIAL_NORMAL_MAP + T = -T; + B = -B; +#endif +#endif } - // # ifdef STANDARDMATERIAL_NORMAL_MAP - // T = gl_FrontFacing ? T : -T; - // B = gl_FrontFacing ? B : -B; - // # endif } - // # ifdef STANDARDMATERIAL_NORMAL_MAP - // mat3 TBN = mat3(T, B, N); - // N = TBN * normalize(texture(sampler2D(normal_map, normal_map_sampler), v_Uv).rgb * 2.0 - 1.0); - // # endif +#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); +#endif +#endif var V: vec3; if (view.projection.w.w != 1.0) { // If the projection is not orthographic diff --git a/pipelined/bevy_render2/src/mesh/mesh/mod.rs b/pipelined/bevy_render2/src/mesh/mesh/mod.rs index 72f1e29a92c174..c034768484e866 100644 --- a/pipelined/bevy_render2/src/mesh/mesh/mod.rs +++ b/pipelined/bevy_render2/src/mesh/mesh/mod.rs @@ -9,7 +9,7 @@ use bevy_core::cast_slice; use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_math::*; use bevy_reflect::TypeUuid; -use bevy_utils::EnumVariantMeta; +use bevy_utils::{EnumVariantMeta, HashSet}; use std::{borrow::Cow, collections::BTreeMap}; use wgpu::{ util::BufferInitDescriptor, BufferUsages, IndexFormat, PrimitiveTopology, VertexFormat, @@ -532,6 +532,7 @@ impl From<&Indices> for IndexFormat { pub struct GpuMesh { pub vertex_buffer: Buffer, pub index_info: Option, + pub vertex_attributes: HashSet>, } #[derive(Debug, Clone)] @@ -574,6 +575,7 @@ impl RenderAsset for Mesh { Ok(GpuMesh { vertex_buffer, index_info, + vertex_attributes: mesh.attributes.keys().cloned().collect(), }) } }