Support for normal maps including from glTF models (#2741)
# Objective - Support tangent vertex attributes, and normal maps - Support loading these from glTF models ## Solution - Make two pipelines in both the shadow and pbr passes, one for without normal maps, one for with normal maps - Select the correct pipeline to bind based on the presence of the normal map texture - Share the vertex attribute layout between shadow and pbr passes - Refactored pbr.wgsl to share a bunch of common code between the normal map and non-normal map entry points. I tried to do this in a way that will allow custom shader reuse. Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
		
							parent
							
								
									85487707ef
								
							
						
					
					
						commit
						09706cdb2a
					
				| @ -128,12 +128,12 @@ async fn load_gltf<'a, 'b>( | |||||||
|                 mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute); |                 mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // if let Some(vertex_attribute) = reader
 |             if let Some(vertex_attribute) = reader | ||||||
|             //     .read_tangents()
 |                 .read_tangents() | ||||||
|             //     .map(|v| VertexAttributeValues::Float32x4(v.collect()))
 |                 .map(|v| VertexAttributeValues::Float32x4(v.collect())) | ||||||
|             // {
 |             { | ||||||
|             //     mesh.set_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute);
 |                 mesh.set_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute); | ||||||
|             // }
 |             } | ||||||
| 
 | 
 | ||||||
|             if let Some(vertex_attribute) = reader |             if let Some(vertex_attribute) = reader | ||||||
|                 .read_tex_coords(0) |                 .read_tex_coords(0) | ||||||
| @ -382,15 +382,16 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle< | |||||||
|         None |         None | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     // let normal_map: Option<Handle<Texture>> = if let Some(normal_texture) = material.normal_texture() {
 |     let normal_map_texture: Option<Handle<Image>> = | ||||||
|     //     // TODO: handle normal_texture.scale
 |         if let Some(normal_texture) = material.normal_texture() { | ||||||
|     //     // TODO: handle normal_texture.tex_coord() (the *set* index for the right texcoords)
 |             // TODO: handle normal_texture.scale
 | ||||||
|     //     let label = texture_label(&normal_texture.texture());
 |             // TODO: handle normal_texture.tex_coord() (the *set* index for the right texcoords)
 | ||||||
|     //     let path = AssetPath::new_ref(load_context.path(), Some(&label));
 |             let label = texture_label(&normal_texture.texture()); | ||||||
|     //     Some(load_context.get_handle(path))
 |             let path = AssetPath::new_ref(load_context.path(), Some(&label)); | ||||||
|     // } else {
 |             Some(load_context.get_handle(path)) | ||||||
|     //     None
 |         } else { | ||||||
|     // };
 |             None | ||||||
|  |         }; | ||||||
| 
 | 
 | ||||||
|     let metallic_roughness_texture = if let Some(info) = pbr.metallic_roughness_texture() { |     let metallic_roughness_texture = if let Some(info) = pbr.metallic_roughness_texture() { | ||||||
|         // TODO: handle info.tex_coord() (the *set* index for the right texcoords)
 |         // 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(), |             perceptual_roughness: pbr.roughness_factor(), | ||||||
|             metallic: pbr.metallic_factor(), |             metallic: pbr.metallic_factor(), | ||||||
|             metallic_roughness_texture, |             metallic_roughness_texture, | ||||||
|             // normal_map,
 |             normal_map_texture, | ||||||
|             double_sided: material.double_sided(), |             double_sided: material.double_sided(), | ||||||
|             occlusion_texture, |             occlusion_texture, | ||||||
|             emissive: Color::rgba(emissive[0], emissive[1], emissive[2], 1.0), |             emissive: Color::rgba(emissive[0], emissive[1], emissive[2], 1.0), | ||||||
|  | |||||||
| @ -70,7 +70,8 @@ impl Plugin for PbrPlugin { | |||||||
|             .init_resource::<ShadowPipeline>() |             .init_resource::<ShadowPipeline>() | ||||||
|             .init_resource::<DrawFunctions<Shadow>>() |             .init_resource::<DrawFunctions<Shadow>>() | ||||||
|             .init_resource::<LightMeta>() |             .init_resource::<LightMeta>() | ||||||
|             .init_resource::<SpecializedPipelines<PbrPipeline>>(); |             .init_resource::<SpecializedPipelines<PbrPipeline>>() | ||||||
|  |             .init_resource::<SpecializedPipelines<ShadowPipeline>>(); | ||||||
| 
 | 
 | ||||||
|         let draw_shadow_mesh = DrawShadowMesh::new(&mut render_app.world); |         let draw_shadow_mesh = DrawShadowMesh::new(&mut render_app.world); | ||||||
|         let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); |         let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| use crate::PbrPipeline; | use crate::{PbrPipeline, StandardMaterialFlags}; | ||||||
| use bevy_app::{App, Plugin}; | use bevy_app::{App, Plugin}; | ||||||
| use bevy_asset::{AddAsset, Handle}; | use bevy_asset::{AddAsset, Handle}; | ||||||
| use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; | use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; | ||||||
| @ -16,21 +16,6 @@ use bevy_render2::{ | |||||||
| use crevice::std140::{AsStd140, Std140}; | use crevice::std140::{AsStd140, Std140}; | ||||||
| use wgpu::{BindGroupDescriptor, BindGroupEntry, BindingResource}; | 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
 | /// A material with "standard" properties used in PBR lighting
 | ||||||
| /// Standard property values with pictures here https://google.github.io/filament/Material%20Properties.pdf
 | /// Standard property values with pictures here https://google.github.io/filament/Material%20Properties.pdf
 | ||||||
| #[derive(Debug, Clone, TypeUuid)] | #[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]
 |     /// 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
 |     /// defaults to 0.5 which is mapped to 4% reflectance in the shader
 | ||||||
|     pub reflectance: f32, |     pub reflectance: f32, | ||||||
|  |     pub normal_map_texture: Option<Handle<Image>>, | ||||||
|     pub occlusion_texture: Option<Handle<Image>>, |     pub occlusion_texture: Option<Handle<Image>>, | ||||||
|     pub double_sided: bool, |     pub double_sided: bool, | ||||||
|     pub unlit: 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
 |             // Expressed in a linear scale and equivalent to 4% reflectance see https://google.github.io/filament/Material%20Properties.pdf
 | ||||||
|             reflectance: 0.5, |             reflectance: 0.5, | ||||||
|             occlusion_texture: None, |             occlusion_texture: None, | ||||||
|  |             normal_map_texture: None, | ||||||
|             double_sided: false, |             double_sided: false, | ||||||
|             unlit: false, |             unlit: false, | ||||||
|         } |         } | ||||||
| @ -140,6 +127,7 @@ impl Plugin for StandardMaterialPlugin { | |||||||
| pub struct GpuStandardMaterial { | pub struct GpuStandardMaterial { | ||||||
|     pub buffer: Buffer, |     pub buffer: Buffer, | ||||||
|     pub bind_group: BindGroup, |     pub bind_group: BindGroup, | ||||||
|  |     pub flags: StandardMaterialFlags, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl RenderAsset for StandardMaterial { | impl RenderAsset for StandardMaterial { | ||||||
| @ -185,6 +173,13 @@ impl RenderAsset for StandardMaterial { | |||||||
|         } else { |         } else { | ||||||
|             return Err(PrepareAssetError::RetryNextUpdate(material)); |             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) = |         let (occlusion_texture_view, occlusion_sampler) = if let Some(result) = | ||||||
|             image_handle_to_view_sampler(pbr_pipeline, gpu_images, &material.occlusion_texture) |             image_handle_to_view_sampler(pbr_pipeline, gpu_images, &material.occlusion_texture) | ||||||
|         { |         { | ||||||
| @ -211,13 +206,16 @@ impl RenderAsset for StandardMaterial { | |||||||
|         if material.unlit { |         if material.unlit { | ||||||
|             flags |= StandardMaterialFlags::UNLIT; |             flags |= StandardMaterialFlags::UNLIT; | ||||||
|         } |         } | ||||||
|  |         if material.normal_map_texture.is_some() { | ||||||
|  |             flags |= StandardMaterialFlags::NORMAL_MAP_TEXTURE; | ||||||
|  |         } | ||||||
|         let value = StandardMaterialUniformData { |         let value = StandardMaterialUniformData { | ||||||
|             base_color: material.base_color.as_rgba_linear().into(), |             base_color: material.base_color.as_rgba_linear().into(), | ||||||
|             emissive: material.emissive.into(), |             emissive: material.emissive.into(), | ||||||
|             roughness: material.perceptual_roughness, |             roughness: material.perceptual_roughness, | ||||||
|             metallic: material.metallic, |             metallic: material.metallic, | ||||||
|             reflectance: material.reflectance, |             reflectance: material.reflectance, | ||||||
|             flags: flags.bits, |             flags: flags.bits(), | ||||||
|         }; |         }; | ||||||
|         let value_std140 = value.as_std140(); |         let value_std140 = value.as_std140(); | ||||||
| 
 | 
 | ||||||
| @ -264,12 +262,24 @@ impl RenderAsset for StandardMaterial { | |||||||
|                     binding: 8, |                     binding: 8, | ||||||
|                     resource: BindingResource::Sampler(occlusion_sampler), |                     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"), |             label: Some("pbr_standard_material_bind_group"), | ||||||
|             layout: &pbr_pipeline.material_layout, |             layout: &pbr_pipeline.material_layout, | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         Ok(GpuStandardMaterial { buffer, bind_group }) |         Ok(GpuStandardMaterial { | ||||||
|  |             buffer, | ||||||
|  |             bind_group, | ||||||
|  |             flags, | ||||||
|  |         }) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -101,8 +101,8 @@ pub const DIRECTIONAL_SHADOW_LAYERS: u32 = MAX_DIRECTIONAL_LIGHTS as u32; | |||||||
| pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float; | pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float; | ||||||
| 
 | 
 | ||||||
| pub struct ShadowPipeline { | pub struct ShadowPipeline { | ||||||
|     pub pipeline: CachedPipelineId, |  | ||||||
|     pub view_layout: BindGroupLayout, |     pub view_layout: BindGroupLayout, | ||||||
|  |     pub mesh_layout: BindGroupLayout, | ||||||
|     pub point_light_sampler: Sampler, |     pub point_light_sampler: Sampler, | ||||||
|     pub directional_light_sampler: Sampler, |     pub directional_light_sampler: Sampler, | ||||||
| } | } | ||||||
| @ -133,15 +133,81 @@ impl FromWorld for ShadowPipeline { | |||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         let pbr_pipeline = world.get_resource::<PbrPipeline>().unwrap(); |         let pbr_pipeline = world.get_resource::<PbrPipeline>().unwrap(); | ||||||
|         let descriptor = RenderPipelineDescriptor { | 
 | ||||||
|             vertex: VertexState { |         ShadowPipeline { | ||||||
|                 shader: SHADOW_SHADER_HANDLE.typed::<Shader>(), |             view_layout, | ||||||
|                 entry_point: "vertex".into(), |             mesh_layout: pbr_pipeline.mesh_layout.clone(), | ||||||
|                 shader_defs: vec![], |             point_light_sampler: render_device.create_sampler(&SamplerDescriptor { | ||||||
|                 buffers: vec![VertexBufferLayout { |                 address_mode_u: AddressMode::ClampToEdge, | ||||||
|                     array_stride: 32, |                 address_mode_v: AddressMode::ClampToEdge, | ||||||
|                     step_mode: VertexStepMode::Vertex, |                 address_mode_w: AddressMode::ClampToEdge, | ||||||
|                     attributes: vec![ |                 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))
 |                         // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically))
 | ||||||
|                         VertexAttribute { |                         VertexAttribute { | ||||||
|                             format: VertexFormat::Float32x3, |                             format: VertexFormat::Float32x3, | ||||||
| @ -161,10 +227,21 @@ impl FromWorld for ShadowPipeline { | |||||||
|                             shader_location: 2, |                             shader_location: 2, | ||||||
|                         }, |                         }, | ||||||
|                     ], |                     ], | ||||||
|  |                 ) | ||||||
|  |             }; | ||||||
|  |         RenderPipelineDescriptor { | ||||||
|  |             vertex: VertexState { | ||||||
|  |                 shader: SHADOW_SHADER_HANDLE.typed::<Shader>(), | ||||||
|  |                 entry_point: "vertex".into(), | ||||||
|  |                 shader_defs: vec![], | ||||||
|  |                 buffers: vec![VertexBufferLayout { | ||||||
|  |                     array_stride: vertex_array_stride, | ||||||
|  |                     step_mode: VertexStepMode::Vertex, | ||||||
|  |                     attributes: vertex_attributes, | ||||||
|                 }], |                 }], | ||||||
|             }, |             }, | ||||||
|             fragment: None, |             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 { |             primitive: PrimitiveState { | ||||||
|                 topology: PrimitiveTopology::TriangleList, |                 topology: PrimitiveTopology::TriangleList, | ||||||
|                 strip_index_format: None, |                 strip_index_format: None, | ||||||
| @ -192,32 +269,6 @@ impl FromWorld for ShadowPipeline { | |||||||
|             }), |             }), | ||||||
|             multisample: MultisampleState::default(), |             multisample: MultisampleState::default(), | ||||||
|             label: Some("shadow_pipeline".into()), |             label: Some("shadow_pipeline".into()), | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         let mut render_pipeline_cache = world.get_resource_mut::<RenderPipelineCache>().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() |  | ||||||
|             }), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -626,10 +677,14 @@ pub fn queue_shadow_view_bind_group( | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[allow(clippy::too_many_arguments)] | ||||||
| pub fn queue_shadows( | pub fn queue_shadows( | ||||||
|     shadow_draw_functions: Res<DrawFunctions<Shadow>>, |     shadow_draw_functions: Res<DrawFunctions<Shadow>>, | ||||||
|     shadow_pipeline: Res<ShadowPipeline>, |     shadow_pipeline: Res<ShadowPipeline>, | ||||||
|     casting_meshes: Query<Entity, (With<Handle<Mesh>>, Without<NotShadowCaster>)>, |     casting_meshes: Query<(Entity, &Handle<Mesh>), Without<NotShadowCaster>>, | ||||||
|  |     render_meshes: Res<RenderAssets<Mesh>>, | ||||||
|  |     mut pipelines: ResMut<SpecializedPipelines<ShadowPipeline>>, | ||||||
|  |     mut pipeline_cache: ResMut<RenderPipelineCache>, | ||||||
|     mut view_lights: Query<&ViewLights>, |     mut view_lights: Query<&ViewLights>, | ||||||
|     mut view_light_shadow_phases: Query<&mut RenderPhase<Shadow>>, |     mut view_light_shadow_phases: Query<&mut RenderPhase<Shadow>>, | ||||||
| ) { | ) { | ||||||
| @ -642,10 +697,18 @@ pub fn queue_shadows( | |||||||
|         for view_light_entity in view_lights.lights.iter().copied() { |         for view_light_entity in view_lights.lights.iter().copied() { | ||||||
|             let mut shadow_phase = view_light_shadow_phases.get_mut(view_light_entity).unwrap(); |             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"
 |             // 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.has_tangents { | ||||||
|  |                         key |= ShadowPipelineKey::VERTEX_TANGENTS; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 let pipeline_id = pipelines.specialize(&mut pipeline_cache, &shadow_pipeline, key); | ||||||
|  | 
 | ||||||
|                 shadow_phase.add(Shadow { |                 shadow_phase.add(Shadow { | ||||||
|                     draw_function: draw_shadow_mesh, |                     draw_function: draw_shadow_mesh, | ||||||
|                     pipeline: shadow_pipeline.pipeline, |                     pipeline: pipeline_id, | ||||||
|                     entity, |                     entity, | ||||||
|                     distance: 0.0, // TODO: sort back-to-front
 |                     distance: 0.0, // TODO: sort back-to-front
 | ||||||
|                 }) |                 }) | ||||||
|  | |||||||
| @ -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( | pub fn extract_meshes( | ||||||
|     mut commands: Commands, |     mut commands: Commands, | ||||||
|     mut previous_caster_len: Local<usize>, |     mut previous_caster_len: Local<usize>, | ||||||
| @ -300,6 +316,27 @@ impl FromWorld for PbrPipeline { | |||||||
|                     }, |                     }, | ||||||
|                     count: None, |                     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"), |             label: Some("pbr_material_layout"), | ||||||
|         }); |         }); | ||||||
| @ -375,6 +412,8 @@ bitflags::bitflags! { | |||||||
|     /// MSAA uses the highest 6 bits for the MSAA sample count - 1 to 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 { |     pub struct PbrPipelineKey: u32 { | ||||||
|         const NONE                        = 0; |         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; |         const MSAA_RESERVED_BITS          = PbrPipelineKey::MSAA_MASK_BITS << PbrPipelineKey::MSAA_SHIFT_BITS; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -397,15 +436,41 @@ impl SpecializedPipeline for PbrPipeline { | |||||||
|     type Key = PbrPipelineKey; |     type Key = PbrPipelineKey; | ||||||
| 
 | 
 | ||||||
|     fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { |     fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { | ||||||
|         RenderPipelineDescriptor { |         let (vertex_array_stride, vertex_attributes) = | ||||||
|             vertex: VertexState { |             if key.contains(PbrPipelineKey::VERTEX_TANGENTS) { | ||||||
|                 shader: PBR_SHADER_HANDLE.typed::<Shader>(), |                 ( | ||||||
|                 entry_point: "vertex".into(), |                     48, | ||||||
|                 shader_defs: vec![], |                     vec![ | ||||||
|                 buffers: vec![VertexBufferLayout { |                         // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically))
 | ||||||
|                     array_stride: 32, |                         VertexAttribute { | ||||||
|                     step_mode: VertexStepMode::Vertex, |                             format: VertexFormat::Float32x3, | ||||||
|                     attributes: vec![ |                             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))
 |                         // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically))
 | ||||||
|                         VertexAttribute { |                         VertexAttribute { | ||||||
|                             format: VertexFormat::Float32x3, |                             format: VertexFormat::Float32x3, | ||||||
| @ -425,11 +490,29 @@ impl SpecializedPipeline for PbrPipeline { | |||||||
|                             shader_location: 2, |                             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::<Shader>(), | ||||||
|  |                 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 { |             fragment: Some(FragmentState { | ||||||
|                 shader: PBR_SHADER_HANDLE.typed::<Shader>(), |                 shader: PBR_SHADER_HANDLE.typed::<Shader>(), | ||||||
|                 shader_defs: vec![], |                 shader_defs, | ||||||
|                 entry_point: "fragment".into(), |                 entry_point: "fragment".into(), | ||||||
|                 targets: vec![ColorTargetState { |                 targets: vec![ColorTargetState { | ||||||
|                     format: TextureFormat::bevy_default(), |                     format: TextureFormat::bevy_default(), | ||||||
| @ -528,11 +611,14 @@ pub fn queue_meshes( | |||||||
|     light_meta: Res<LightMeta>, |     light_meta: Res<LightMeta>, | ||||||
|     msaa: Res<Msaa>, |     msaa: Res<Msaa>, | ||||||
|     view_uniforms: Res<ViewUniforms>, |     view_uniforms: Res<ViewUniforms>, | ||||||
|  |     render_meshes: Res<RenderAssets<Mesh>>, | ||||||
|     render_materials: Res<RenderAssets<StandardMaterial>>, |     render_materials: Res<RenderAssets<StandardMaterial>>, | ||||||
|     standard_material_meshes: Query< |     standard_material_meshes: Query<( | ||||||
|         (Entity, &Handle<StandardMaterial>, &MeshUniform), |         Entity, | ||||||
|         With<Handle<Mesh>>, |         &Handle<StandardMaterial>, | ||||||
|     >, |         &Handle<Mesh>, | ||||||
|  |         &MeshUniform, | ||||||
|  |     )>, | ||||||
|     mut views: Query<( |     mut views: Query<( | ||||||
|         Entity, |         Entity, | ||||||
|         &ExtractedView, |         &ExtractedView, | ||||||
| @ -544,7 +630,6 @@ pub fn queue_meshes( | |||||||
|         view_uniforms.uniforms.binding(), |         view_uniforms.uniforms.binding(), | ||||||
|         light_meta.view_gpu_lights.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() { |         for (entity, view, view_lights, mut transparent_phase) in views.iter_mut() { | ||||||
|             let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor { |             let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor { | ||||||
|                 entries: &[ |                 entries: &[ | ||||||
| @ -595,13 +680,27 @@ pub fn queue_meshes( | |||||||
|             let view_matrix = view.transform.compute_matrix(); |             let view_matrix = view.transform.compute_matrix(); | ||||||
|             let view_row_2 = view_matrix.row(2); |             let view_row_2 = view_matrix.row(2); | ||||||
| 
 | 
 | ||||||
|             for (entity, material_handle, mesh_uniform) in standard_material_meshes.iter() { |             for (entity, material_handle, mesh_handle, mesh_uniform) in | ||||||
|                 if !render_materials.contains_key(material_handle) { |                 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; |                     continue; | ||||||
|                 } |                 } | ||||||
|  |                 if let Some(mesh) = render_meshes.get(mesh_handle) { | ||||||
|  |                     if mesh.has_tangents { | ||||||
|  |                         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
 |                 // 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
 |                 //       gives the z component of translation of the mesh in view space
 | ||||||
|                 let mesh_z = view_row_2.dot(mesh_uniform.transform.col(3)); |                 let mesh_z = view_row_2.dot(mesh_uniform.transform.col(3)); | ||||||
|  | |||||||
| @ -26,6 +26,9 @@ struct Vertex { | |||||||
|     [[location(0)]] position: vec3<f32>; |     [[location(0)]] position: vec3<f32>; | ||||||
|     [[location(1)]] normal: vec3<f32>; |     [[location(1)]] normal: vec3<f32>; | ||||||
|     [[location(2)]] uv: vec2<f32>; |     [[location(2)]] uv: vec2<f32>; | ||||||
|  | #ifdef VERTEX_TANGENTS | ||||||
|  |     [[location(3)]] tangent: vec4<f32>; | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| struct VertexOutput { | struct VertexOutput { | ||||||
| @ -33,6 +36,9 @@ struct VertexOutput { | |||||||
|     [[location(0)]] world_position: vec4<f32>; |     [[location(0)]] world_position: vec4<f32>; | ||||||
|     [[location(1)]] world_normal: vec3<f32>; |     [[location(1)]] world_normal: vec3<f32>; | ||||||
|     [[location(2)]] uv: vec2<f32>; |     [[location(2)]] uv: vec2<f32>; | ||||||
|  | #ifdef VERTEX_TANGENTS | ||||||
|  |     [[location(3)]] world_tangent: vec4<f32>; | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| [[stage(vertex)]] | [[stage(vertex)]] | ||||||
| @ -48,6 +54,16 @@ fn vertex(vertex: Vertex) -> VertexOutput { | |||||||
|         mesh.inverse_transpose_model.y.xyz, |         mesh.inverse_transpose_model.y.xyz, | ||||||
|         mesh.inverse_transpose_model.z.xyz |         mesh.inverse_transpose_model.z.xyz | ||||||
|     ) * vertex.normal; |     ) * vertex.normal; | ||||||
|  | #ifdef VERTEX_TANGENTS | ||||||
|  |     out.world_tangent = vec4<f32>( | ||||||
|  |         mat3x3<f32>( | ||||||
|  |             mesh.model.x.xyz, | ||||||
|  |             mesh.model.y.xyz, | ||||||
|  |             mesh.model.z.xyz | ||||||
|  |         ) * vertex.tangent.xyz, | ||||||
|  |         vertex.tangent.w | ||||||
|  |     ); | ||||||
|  | #endif | ||||||
|     return out; |     return out; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -164,6 +180,10 @@ var metallic_roughness_sampler: sampler; | |||||||
| var occlusion_texture: texture_2d<f32>; | var occlusion_texture: texture_2d<f32>; | ||||||
| [[group(1), binding(8)]] | [[group(1), binding(8)]] | ||||||
| var occlusion_sampler: sampler; | var occlusion_sampler: sampler; | ||||||
|  | [[group(1), binding(9)]] | ||||||
|  | var normal_map_texture: texture_2d<f32>; | ||||||
|  | [[group(1), binding(10)]] | ||||||
|  | var normal_map_sampler: sampler; | ||||||
| 
 | 
 | ||||||
| let PI: f32 = 3.141592653589793; | let PI: f32 = 3.141592653589793; | ||||||
| 
 | 
 | ||||||
| @ -475,6 +495,9 @@ struct FragmentInput { | |||||||
|     [[location(0)]] world_position: vec4<f32>; |     [[location(0)]] world_position: vec4<f32>; | ||||||
|     [[location(1)]] world_normal: vec3<f32>; |     [[location(1)]] world_normal: vec3<f32>; | ||||||
|     [[location(2)]] uv: vec2<f32>; |     [[location(2)]] uv: vec2<f32>; | ||||||
|  | #ifdef VERTEX_TANGENTS | ||||||
|  |     [[location(3)]] world_tangent: vec4<f32>; | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| [[stage(fragment)]] | [[stage(fragment)]] | ||||||
| @ -510,27 +533,31 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> { | |||||||
| 
 | 
 | ||||||
|         var N: vec3<f32> = normalize(in.world_normal); |         var N: vec3<f32> = normalize(in.world_normal); | ||||||
| 
 | 
 | ||||||
|         // FIXME: Normal maps need an additional vertex attribute and vertex stage output/fragment stage input | #ifdef VERTEX_TANGENTS | ||||||
|         //        Just use a separate shader for lit with normal maps? | #ifdef STANDARDMATERIAL_NORMAL_MAP | ||||||
|         // #    ifdef STANDARDMATERIAL_NORMAL_MAP |         var T: vec3<f32> = normalize(in.world_tangent.xyz - N * dot(in.world_tangent.xyz, N)); | ||||||
|         //     vec3 T = normalize(v_WorldTangent.xyz); |         var B: vec3<f32> = cross(N, T) * in.world_tangent.w; | ||||||
|         //     vec3 B = cross(N, T) * v_WorldTangent.w; | #endif | ||||||
|         // #    endif | #endif | ||||||
| 
 | 
 | ||||||
|         if ((material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u) { |         if ((material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u) { | ||||||
|             if (!in.is_front) { |             if (!in.is_front) { | ||||||
|                 N = -N; |                 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 | #ifdef VERTEX_TANGENTS | ||||||
|         //     mat3 TBN = mat3(T, B, N); | #ifdef STANDARDMATERIAL_NORMAL_MAP | ||||||
|         //     N = TBN * normalize(texture(sampler2D(normal_map, normal_map_sampler), v_Uv).rgb * 2.0 - 1.0); |         let TBN = mat3x3<f32>(T, B, N); | ||||||
|         // #    endif |         N = TBN * normalize(textureSample(normal_map_texture, normal_map_sampler, in.uv).rgb * 2.0 - 1.0); | ||||||
|  | #endif | ||||||
|  | #endif | ||||||
| 
 | 
 | ||||||
|         var V: vec3<f32>; |         var V: vec3<f32>; | ||||||
|         if (view.projection.w.w != 1.0) { // If the projection is not orthographic |         if (view.projection.w.w != 1.0) { // If the projection is not orthographic | ||||||
|  | |||||||
| @ -532,6 +532,7 @@ impl From<&Indices> for IndexFormat { | |||||||
| pub struct GpuMesh { | pub struct GpuMesh { | ||||||
|     pub vertex_buffer: Buffer, |     pub vertex_buffer: Buffer, | ||||||
|     pub index_info: Option<GpuIndexInfo>, |     pub index_info: Option<GpuIndexInfo>, | ||||||
|  |     pub has_tangents: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
| @ -574,6 +575,7 @@ impl RenderAsset for Mesh { | |||||||
|         Ok(GpuMesh { |         Ok(GpuMesh { | ||||||
|             vertex_buffer, |             vertex_buffer, | ||||||
|             index_info, |             index_info, | ||||||
|  |             has_tangents: mesh.attributes.contains_key(Mesh::ATTRIBUTE_TANGENT), | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Robert Swain
						Robert Swain