mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 06:00:20 +00:00
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
7 changed files with 313 additions and 110 deletions
|
@ -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"),
|
||||||
});
|
});
|
||||||
|
@ -374,8 +411,10 @@ bitflags::bitflags! {
|
||||||
// NOTE: Apparently quadro drivers support up to 64x MSAA.
|
// 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.
|
/// 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 MSAA_RESERVED_BITS = PbrPipelineKey::MSAA_MASK_BITS << PbrPipelineKey::MSAA_SHIFT_BITS;
|
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;
|
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 a new issue