Skip to content

Commit

Permalink
bevy_pbr2: Support vertex tangents and normal maps
Browse files Browse the repository at this point in the history
  • Loading branch information
superdump committed Oct 28, 2021
1 parent 383d7a0 commit ce49e58
Show file tree
Hide file tree
Showing 7 changed files with 314 additions and 112 deletions.
33 changes: 17 additions & 16 deletions pipelined/bevy_gltf2/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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),
Expand Down
3 changes: 2 additions & 1 deletion pipelined/bevy_pbr2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
46 changes: 28 additions & 18 deletions pipelined/bevy_pbr2/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,7 @@ use bevy_render2::{
use crevice::std140::{AsStd140, Std140};
use wgpu::{BindGroupDescriptor, BindGroupEntry, BindingResource};

use crate::PbrPipeline;

// 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;
}
}
use crate::{PbrPipeline, StandardMaterialFlags};

/// A material with "standard" properties used in PBR lighting
/// Standard property values with pictures here https://google.github.io/filament/Material%20Properties.pdf
Expand Down Expand Up @@ -59,6 +44,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,
Expand All @@ -85,6 +71,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,
}
Expand Down Expand Up @@ -141,6 +128,7 @@ impl Plugin for StandardMaterialPlugin {
pub struct GpuStandardMaterial {
pub buffer: Buffer,
pub bind_group: BindGroup,
pub flags: StandardMaterialFlags,
}

impl RenderAsset for StandardMaterial {
Expand Down Expand Up @@ -186,6 +174,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)
{
Expand All @@ -212,13 +207,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();

Expand Down Expand Up @@ -265,12 +263,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,
})
}
}

Expand Down
142 changes: 102 additions & 40 deletions pipelined/bevy_pbr2/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -133,15 +133,81 @@ impl FromWorld for ShadowPipeline {
});

let pbr_pipeline = world.get_resource::<PbrPipeline>().unwrap();
let descriptor = OwnedRenderPipelineDescriptor {
vertex: OwnedVertexState {
shader: SHADOW_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(),
shader_defs: vec![],
buffers: vec![OwnedVertexBufferLayout {
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) -> OwnedRenderPipelineDescriptor {
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,
Expand All @@ -161,10 +227,21 @@ impl FromWorld for ShadowPipeline {
shader_location: 2,
},
],
)
};
OwnedRenderPipelineDescriptor {
vertex: OwnedVertexState {
shader: SHADOW_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(),
shader_defs: vec![],
buffers: vec![OwnedVertexBufferLayout {
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,
Expand Down Expand Up @@ -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()
}),
}
}
}
Expand Down Expand Up @@ -630,7 +681,10 @@ pub fn queue_shadow_view_bind_group(
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>>,
) {
Expand All @@ -643,10 +697,18 @@ pub fn queue_shadows(
for view_light_entity in view_lights.lights.iter().copied() {
let mut shadow_phase = view_light_shadow_phases.get_mut(view_light_entity).unwrap();
// TODO: this should only queue up meshes that are actually visible by each "light view"
for entity in casting_meshes.iter() {
for (entity, mesh_handle) in casting_meshes.iter() {
let mut key = ShadowPipelineKey::empty();
if let Some(mesh) = render_meshes.get(mesh_handle) {
if mesh.vertex_attributes.contains(Mesh::ATTRIBUTE_TANGENT) {
key |= ShadowPipelineKey::VERTEX_TANGENTS;
}
}
let pipeline_id = pipelines.specialize(&mut pipeline_cache, &shadow_pipeline, key);

shadow_phase.add(Shadow {
draw_function: draw_shadow_mesh,
pipeline: shadow_pipeline.pipeline,
pipeline: pipeline_id,
entity,
distance: 0.0, // TODO: sort back-to-front
})
Expand Down
Loading

0 comments on commit ce49e58

Please sign in to comment.