mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +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);
|
||||
}
|
||||
|
||||
// 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<Handle<Texture>> = 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<Handle<Image>> =
|
||||
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),
|
||||
|
|
|
@ -70,7 +70,8 @@ impl Plugin for PbrPlugin {
|
|||
.init_resource::<ShadowPipeline>()
|
||||
.init_resource::<DrawFunctions<Shadow>>()
|
||||
.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 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_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<Handle<Image>>,
|
||||
pub occlusion_texture: Option<Handle<Image>>,
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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::<PbrPipeline>().unwrap();
|
||||
let descriptor = RenderPipelineDescriptor {
|
||||
vertex: VertexState {
|
||||
shader: SHADOW_SHADER_HANDLE.typed::<Shader>(),
|
||||
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::<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,
|
||||
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::<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(
|
||||
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
|
||||
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_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() {
|
||||
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.has_tangents {
|
||||
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
|
||||
})
|
||||
|
|
|
@ -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<usize>,
|
||||
|
@ -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"),
|
||||
});
|
||||
|
@ -375,6 +412,8 @@ bitflags::bitflags! {
|
|||
/// 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 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::<Shader>(),
|
||||
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::<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 {
|
||||
shader: PBR_SHADER_HANDLE.typed::<Shader>(),
|
||||
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<LightMeta>,
|
||||
msaa: Res<Msaa>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
render_materials: Res<RenderAssets<StandardMaterial>>,
|
||||
standard_material_meshes: Query<
|
||||
(Entity, &Handle<StandardMaterial>, &MeshUniform),
|
||||
With<Handle<Mesh>>,
|
||||
>,
|
||||
standard_material_meshes: Query<(
|
||||
Entity,
|
||||
&Handle<StandardMaterial>,
|
||||
&Handle<Mesh>,
|
||||
&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.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
|
||||
// gives the z component of translation of the mesh in view space
|
||||
let mesh_z = view_row_2.dot(mesh_uniform.transform.col(3));
|
||||
|
|
|
@ -26,6 +26,9 @@ struct Vertex {
|
|||
[[location(0)]] position: vec3<f32>;
|
||||
[[location(1)]] normal: vec3<f32>;
|
||||
[[location(2)]] uv: vec2<f32>;
|
||||
#ifdef VERTEX_TANGENTS
|
||||
[[location(3)]] tangent: vec4<f32>;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
|
@ -33,6 +36,9 @@ struct VertexOutput {
|
|||
[[location(0)]] world_position: vec4<f32>;
|
||||
[[location(1)]] world_normal: vec3<f32>;
|
||||
[[location(2)]] uv: vec2<f32>;
|
||||
#ifdef VERTEX_TANGENTS
|
||||
[[location(3)]] world_tangent: vec4<f32>;
|
||||
#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<f32>(
|
||||
mat3x3<f32>(
|
||||
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<f32>;
|
||||
[[group(1), binding(8)]]
|
||||
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;
|
||||
|
||||
|
@ -475,6 +495,9 @@ struct FragmentInput {
|
|||
[[location(0)]] world_position: vec4<f32>;
|
||||
[[location(1)]] world_normal: vec3<f32>;
|
||||
[[location(2)]] uv: vec2<f32>;
|
||||
#ifdef VERTEX_TANGENTS
|
||||
[[location(3)]] world_tangent: vec4<f32>;
|
||||
#endif
|
||||
};
|
||||
|
||||
[[stage(fragment)]]
|
||||
|
@ -510,27 +533,31 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
|||
|
||||
var N: vec3<f32> = 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<f32> = normalize(in.world_tangent.xyz - N * dot(in.world_tangent.xyz, N));
|
||||
var B: vec3<f32> = 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<f32>(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<f32>;
|
||||
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 vertex_buffer: Buffer,
|
||||
pub index_info: Option<GpuIndexInfo>,
|
||||
pub has_tangents: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -574,6 +575,7 @@ impl RenderAsset for Mesh {
|
|||
Ok(GpuMesh {
|
||||
vertex_buffer,
|
||||
index_info,
|
||||
has_tangents: mesh.attributes.contains_key(Mesh::ATTRIBUTE_TANGENT),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue