From 2972e4418c99480a5d66de4e5ba536277b8b734d Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sun, 21 Apr 2024 23:08:41 -0700 Subject: [PATCH 1/7] WIP Add support for motion vectors to meshes with skins and/or morph targets --- crates/bevy_core_pipeline/src/lib.rs | 13 +- crates/bevy_core_pipeline/src/prepass/mod.rs | 5 + crates/bevy_core_pipeline/src/taa/mod.rs | 3 +- crates/bevy_pbr/src/render/mesh.rs | 11 +- crates/bevy_pbr/src/render/mesh_bindings.rs | 127 ++++++++++++++++++- crates/bevy_pbr/src/render/morph.rs | 34 ++++- crates/bevy_pbr/src/render/skin.rs | 37 +++++- 7 files changed, 206 insertions(+), 24 deletions(-) diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 9bb44c4e33116..b486aa8b5113b 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -54,13 +54,16 @@ use crate::{ fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE, fxaa::FxaaPlugin, msaa_writeback::MsaaWritebackPlugin, - prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, + prepass::{ + AnimatedMeshMotionVectors, DeferredPrepass, DepthPrepass, MotionVectorPrepass, + NormalPrepass, + }, tonemapping::TonemappingPlugin, upscaling::UpscalingPlugin, }; use bevy_app::{App, Plugin}; use bevy_asset::load_internal_asset; -use bevy_render::prelude::Shader; +use bevy_render::{prelude::Shader, RenderApp}; #[derive(Default)] pub struct CorePipelinePlugin; @@ -91,4 +94,10 @@ impl Plugin for CorePipelinePlugin { CASPlugin, )); } + + fn finish(&self, app: &mut App) { + if let Some(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.init_resource::(); + } + } } diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index 670eca9e8e2d2..053b68284ebc2 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -30,6 +30,7 @@ pub mod node; use std::ops::Range; use bevy_asset::AssetId; +use bevy_derive::Deref; use bevy_ecs::prelude::*; use bevy_reflect::Reflect; use bevy_render::{ @@ -61,6 +62,10 @@ pub struct MotionVectorPrepass; #[derive(Component, Default, Reflect)] pub struct DeferredPrepass; +/// TODO: Docs +#[derive(Resource, Default, Deref)] +pub struct AnimatedMeshMotionVectors(pub bool); + /// Textures that are written to by the prepass. /// /// This component will only be present if any of the relevant prepass components are also present. diff --git a/crates/bevy_core_pipeline/src/taa/mod.rs b/crates/bevy_core_pipeline/src/taa/mod.rs index 9e76a6d79ba0f..3bcd8652e4b54 100644 --- a/crates/bevy_core_pipeline/src/taa/mod.rs +++ b/crates/bevy_core_pipeline/src/taa/mod.rs @@ -2,7 +2,7 @@ use crate::{ core_3d::graph::{Core3d, Node3d}, fullscreen_vertex_shader::fullscreen_shader_vertex_state, prelude::Camera3d, - prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures}, + prepass::{AnimatedMeshMotionVectors, DepthPrepass, MotionVectorPrepass, ViewPrepassTextures}, }; use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, Handle}; @@ -54,6 +54,7 @@ impl Plugin for TemporalAntiAliasPlugin { }; render_app .init_resource::>() + .insert_resource(AnimatedMeshMotionVectors(true)) .add_systems(ExtractSchedule, extract_taa_settings) .add_systems( Render, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 93b7ed9918730..16c1c96267cca 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -4,6 +4,7 @@ use bevy_asset::{load_internal_asset, AssetId}; use bevy_core_pipeline::{ core_3d::{AlphaMask3d, Opaque3d, Transmissive3d, Transparent3d, CORE_3D_DEPTH_FORMAT}, deferred::{AlphaMask3dDeferred, Opaque3dDeferred}, + prepass::AnimatedMeshMotionVectors, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::entity::EntityHashMap; @@ -839,8 +840,10 @@ impl FromWorld for MeshPipeline { Res, Res, Res, + Res, )> = SystemState::new(world); - let (render_device, default_sampler, render_queue) = system_state.get_mut(world); + let (render_device, default_sampler, render_queue, motion_vector_support) = + system_state.get_mut(world); let clustered_forward_buffer_binding_type = render_device .get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); @@ -885,7 +888,7 @@ impl FromWorld for MeshPipeline { view_layouts, clustered_forward_buffer_binding_type, dummy_white_gpu_image, - mesh_layouts: MeshLayouts::new(&render_device), + mesh_layouts: MeshLayouts::new(&render_device, **motion_vector_support), per_object_buffer_batch_size: GpuArrayBuffer::::batch_size(&render_device), binding_arrays_are_usable: binding_arrays_are_usable(&render_device), } @@ -1694,8 +1697,8 @@ impl RenderCommand

for SetMeshBindGroup { let Some(mesh_asset_id) = mesh_instances.mesh_asset_id(*entity) else { return RenderCommandResult::Success; }; - let skin_index = skin_indices.get(entity); - let morph_index = morph_indices.get(entity); + let skin_index = skin_indices.indices.get(entity); + let morph_index = morph_indices.indices.get(entity); let is_skinned = skin_index.is_some(); let is_morphed = morph_index.is_some(); diff --git a/crates/bevy_pbr/src/render/mesh_bindings.rs b/crates/bevy_pbr/src/render/mesh_bindings.rs index 60beb9911530a..038cae363b904 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_bindings.rs @@ -116,19 +116,34 @@ pub struct MeshLayouts { /// /// [`MorphAttributes`]: bevy_render::mesh::morph::MorphAttributes pub morphed_skinned: BindGroupLayout, + + /// TODO + pub skinned_motion_vectors: Option, + + /// TODO + pub morphed_motion_vectors: Option, + + /// TODO + pub morphed_skinned_motion_vectors: Option, } impl MeshLayouts { /// Prepare the layouts used by the default bevy [`Mesh`]. /// /// [`Mesh`]: bevy_render::prelude::Mesh - pub fn new(render_device: &RenderDevice) -> Self { + pub fn new(render_device: &RenderDevice, motion_vector_support: bool) -> Self { MeshLayouts { model_only: Self::model_only_layout(render_device), lightmapped: Self::lightmapped_layout(render_device), skinned: Self::skinned_layout(render_device), morphed: Self::morphed_layout(render_device), morphed_skinned: Self::morphed_skinned_layout(render_device), + skinned_motion_vectors: motion_vector_support + .then(|| Self::skinned_motion_vectors_layout(render_device)), + morphed_motion_vectors: motion_vector_support + .then(|| Self::morphed_motion_vectors_layout(render_device)), + morphed_skinned_motion_vectors: motion_vector_support + .then(|| Self::morphed_skinned_motion_vectors_layout(render_device)), } } @@ -143,6 +158,19 @@ impl MeshLayouts { ), ) } + fn lightmapped_layout(render_device: &RenderDevice) -> BindGroupLayout { + render_device.create_bind_group_layout( + "lightmapped_mesh_layout", + &BindGroupLayoutEntries::with_indices( + ShaderStages::VERTEX, + ( + (0, layout_entry::model(render_device)), + (4, layout_entry::lightmaps_texture_view()), + (5, layout_entry::lightmaps_sampler()), + ), + ), + ) + } fn skinned_layout(render_device: &RenderDevice) -> BindGroupLayout { render_device.create_bind_group_layout( "skinned_mesh_layout", @@ -182,15 +210,45 @@ impl MeshLayouts { ), ) } - fn lightmapped_layout(render_device: &RenderDevice) -> BindGroupLayout { + fn skinned_motion_vectors_layout(render_device: &RenderDevice) -> BindGroupLayout { render_device.create_bind_group_layout( - "lightmapped_mesh_layout", + "skinned_motion_vectors_mesh_layout", &BindGroupLayoutEntries::with_indices( ShaderStages::VERTEX, ( (0, layout_entry::model(render_device)), - (4, layout_entry::lightmaps_texture_view()), - (5, layout_entry::lightmaps_sampler()), + (1, layout_entry::skinning()), + (6, layout_entry::skinning()), + ), + ), + ) + } + fn morphed_motion_vectors_layout(render_device: &RenderDevice) -> BindGroupLayout { + render_device.create_bind_group_layout( + "morphed_motion_vectors_mesh_layout", + &BindGroupLayoutEntries::with_indices( + ShaderStages::VERTEX, + ( + (0, layout_entry::model(render_device)), + (2, layout_entry::weights()), + (3, layout_entry::targets()), + (7, layout_entry::weights()), + ), + ), + ) + } + fn morphed_skinned_motion_vectors_layout(render_device: &RenderDevice) -> BindGroupLayout { + render_device.create_bind_group_layout( + "morphed_skinned_motion_vectors_mesh_layout", + &BindGroupLayoutEntries::with_indices( + ShaderStages::VERTEX, + ( + (0, layout_entry::model(render_device)), + (1, layout_entry::skinning()), + (2, layout_entry::weights()), + (3, layout_entry::targets()), + (6, layout_entry::skinning()), + (7, layout_entry::weights()), ), ), ) @@ -269,4 +327,63 @@ impl MeshLayouts { ], ) } + pub fn skinned_motion_vectors( + &self, + render_device: &RenderDevice, + model: &BindingResource, + skin: &Buffer, + previous_skin: &Buffer, + ) -> BindGroup { + render_device.create_bind_group( + "skinned_motion_vectors_mesh_bind_group", + self.skinned_motion_vectors.as_ref().unwrap(), + &[ + entry::model(0, model.clone()), + entry::skinning(1, skin), + entry::skinning(6, previous_skin), + ], + ) + } + pub fn morphed_motion_vectors( + &self, + render_device: &RenderDevice, + model: &BindingResource, + weights: &Buffer, + targets: &TextureView, + previous_weights: &Buffer, + ) -> BindGroup { + render_device.create_bind_group( + "morphed_motion_vectors_mesh_bind_group", + self.morphed_motion_vectors.as_ref().unwrap(), + &[ + entry::model(0, model.clone()), + entry::weights(2, weights), + entry::targets(3, targets), + entry::weights(7, previous_weights), + ], + ) + } + pub fn morphed_skinned_motion_vectors( + &self, + render_device: &RenderDevice, + model: &BindingResource, + skin: &Buffer, + weights: &Buffer, + targets: &TextureView, + previous_skin: &Buffer, + previous_weights: &Buffer, + ) -> BindGroup { + render_device.create_bind_group( + "morphed_skinned_motion_vectors_mesh_bind_group", + self.morphed_skinned_motion_vectors.as_ref().unwrap(), + &[ + entry::model(0, model.clone()), + entry::skinning(1, skin), + entry::weights(2, weights), + entry::targets(3, targets), + entry::skinning(6, previous_skin), + entry::weights(7, previous_weights), + ], + ) + } } diff --git a/crates/bevy_pbr/src/render/morph.rs b/crates/bevy_pbr/src/render/morph.rs index b53cf62f2f8e9..7ecb888b1f185 100644 --- a/crates/bevy_pbr/src/render/morph.rs +++ b/crates/bevy_pbr/src/render/morph.rs @@ -1,6 +1,6 @@ use std::{iter, mem}; -use bevy_derive::{Deref, DerefMut}; +use bevy_core_pipeline::prepass::AnimatedMeshMotionVectors; use bevy_ecs::entity::EntityHashMap; use bevy_ecs::prelude::*; use bevy_render::{ @@ -18,18 +18,23 @@ pub struct MorphIndex { pub(super) index: u32, } -#[derive(Default, Resource, Deref, DerefMut)] -pub struct MorphIndices(EntityHashMap); +#[derive(Default, Resource)] +pub struct MorphIndices { + pub indices: EntityHashMap, + pub previous_indices: EntityHashMap, +} #[derive(Resource)] pub struct MorphUniform { pub buffer: BufferVec, + pub previous_buffer: Option>, } impl Default for MorphUniform { fn default() -> Self { Self { buffer: BufferVec::new(BufferUsages::UNIFORM), + previous_buffer: None, } } } @@ -83,9 +88,26 @@ pub fn extract_morphs( mut morph_indices: ResMut, mut uniform: ResMut, query: Extract>, + motion_vector_support: Res, ) { - morph_indices.clear(); - uniform.buffer.clear(); + if **motion_vector_support { + let t = &mut uniform.previous_buffer; + if let Some(buffer) = t { + buffer.clear(); + } + + uniform.previous_buffer = Some(uniform.buffer); + uniform.buffer = t.unwrap_or(BufferVec::new(BufferUsages::UNIFORM)); + + mem::swap( + &mut morph_indices.indices, + &mut morph_indices.previous_indices, + ); + } else { + uniform.buffer.clear(); + } + + morph_indices.indices.clear(); for (entity, view_visibility, morph_weights) in &query { if !view_visibility.get() { @@ -98,7 +120,7 @@ pub fn extract_morphs( add_to_alignment::(&mut uniform.buffer); let index = (start * mem::size_of::()) as u32; - morph_indices.insert(entity, MorphIndex { index }); + morph_indices.indices.insert(entity, MorphIndex { index }); } } diff --git a/crates/bevy_pbr/src/render/skin.rs b/crates/bevy_pbr/src/render/skin.rs index 6a725b546c5e3..bf8b0ef6397ab 100644 --- a/crates/bevy_pbr/src/render/skin.rs +++ b/crates/bevy_pbr/src/render/skin.rs @@ -1,5 +1,7 @@ +use std::mem; + use bevy_asset::Assets; -use bevy_derive::{Deref, DerefMut}; +use bevy_core_pipeline::prepass::AnimatedMeshMotionVectors; use bevy_ecs::entity::EntityHashMap; use bevy_ecs::prelude::*; use bevy_math::Mat4; @@ -30,19 +32,24 @@ impl SkinIndex { } } -#[derive(Default, Resource, Deref, DerefMut)] -pub struct SkinIndices(EntityHashMap); +#[derive(Default, Resource)] +pub struct SkinIndices { + pub indices: EntityHashMap, + pub previous_indices: EntityHashMap, +} // Notes on implementation: see comment on top of the `extract_skins` system. #[derive(Resource)] pub struct SkinUniform { pub buffer: BufferVec, + pub previous_buffer: Option>, } impl Default for SkinUniform { fn default() -> Self { Self { buffer: BufferVec::new(BufferUsages::UNIFORM), + previous_buffer: None, } } } @@ -93,9 +100,27 @@ pub fn extract_skins( query: Extract>, inverse_bindposes: Extract>>, joints: Extract>, + motion_vector_support: Res, ) { - uniform.buffer.clear(); - skin_indices.clear(); + if **motion_vector_support { + let t = &mut uniform.previous_buffer; + if let Some(buffer) = t { + buffer.clear(); + } + + uniform.previous_buffer = Some(uniform.buffer); + uniform.buffer = t.unwrap_or(BufferVec::new(BufferUsages::UNIFORM)); + + mem::swap( + &mut skin_indices.indices, + &mut skin_indices.previous_indices, + ); + } else { + uniform.buffer.clear(); + } + + skin_indices.indices.clear(); + let mut last_start = 0; // PERF: This can be expensive, can we move this to prepare? @@ -130,7 +155,7 @@ pub fn extract_skins( buffer.push(Mat4::ZERO); } - skin_indices.insert(entity, SkinIndex::new(start)); + skin_indices.indices.insert(entity, SkinIndex::new(start)); } // Pad out the buffer to ensure that there's enough space for bindings From 206a0fdfb8fcbce389e60ef28061ab2e0f7128e1 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Tue, 23 Apr 2024 11:30:15 -0700 Subject: [PATCH 2/7] More WIP --- crates/bevy_pbr/src/prepass/mod.rs | 21 ++- .../src/prepass/prepass_bindings.wgsl | 5 + crates/bevy_pbr/src/render/mesh.rs | 130 ++++++++++++------ crates/bevy_pbr/src/render/mod.rs | 1 + crates/bevy_pbr/src/render/morph.wgsl | 3 + crates/bevy_pbr/src/render/skinning.wgsl | 3 + crates/bevy_sprite/src/mesh2d/mesh.rs | 6 - 7 files changed, 115 insertions(+), 54 deletions(-) diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 682935c504c2d..dbc724309ef73 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -438,13 +438,17 @@ where shader_defs.push("PREPASS_FRAGMENT".into()); } - let bind_group = setup_morph_and_skinning_defs( + let (bind_group, animated_mesh_motion_vectors) = setup_morph_and_skinning( &self.mesh_layouts, layout, 5, &key.mesh_key, &mut shader_defs, &mut vertex_attributes, + key.mesh_key + .contains(MeshPipelineKey::ANIMATED_SKIN_MOTION_VECTORS), + key.mesh_key + .contains(MeshPipelineKey::ANIMATED_MORPH_MOTION_VECTORS), ); bind_group_layouts.insert(1, bind_group); @@ -535,11 +539,7 @@ where }; let mut push_constant_ranges = Vec::with_capacity(1); - if cfg!(all( - feature = "webgl", - target_arch = "wasm32", - not(feature = "webgpu") - )) { + if animated_mesh_motion_vectors { push_constant_ranges.push(PushConstantRange { stages: ShaderStages::VERTEX, range: 0..4, @@ -712,6 +712,8 @@ pub fn queue_prepass_material_meshes( render_materials: Res>>, render_material_instances: Res>, render_lightmaps: Res, + skins: Res, + morphs: Res, mut views: Query< ( &ExtractedView, @@ -833,6 +835,13 @@ pub fn queue_prepass_material_meshes( mesh_key |= MeshPipelineKey::LIGHTMAPPED; } + if skins.previous_buffer.is_some() { + mesh_key |= MeshPipelineKey::ANIMATED_SKIN_MOTION_VECTORS; + } + if morphs.previous_buffer.is_some() { + mesh_key |= MeshPipelineKey::ANIMATED_MORPH_MOTION_VECTORS; + } + let pipeline_id = pipelines.specialize( &pipeline_cache, &prepass_pipeline, diff --git a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl index fdc38a4c49654..3e3ebcf8d9606 100644 --- a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl @@ -9,4 +9,9 @@ struct PreviousViewUniforms { @group(0) @binding(2) var previous_view_uniforms: PreviousViewUniforms; #endif // MOTION_VECTOR_PREPASS +// Zero if the current mesh did not have skin/morph data available last frame, else one +#ifdef ANIMATED_MESH_MOTION_VECTORS +var motion_vectors_mask: f32; +#endif + // Material bindings will be in @group(2) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 16c1c96267cca..d48a7d1f5cbc7 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -842,7 +842,7 @@ impl FromWorld for MeshPipeline { Res, Res, )> = SystemState::new(world); - let (render_device, default_sampler, render_queue, motion_vector_support) = + let (render_device, default_sampler, render_queue, animated_mesh_motion_vectors) = system_state.get_mut(world); let clustered_forward_buffer_binding_type = render_device .get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); @@ -888,7 +888,7 @@ impl FromWorld for MeshPipeline { view_layouts, clustered_forward_buffer_binding_type, dummy_white_gpu_image, - mesh_layouts: MeshLayouts::new(&render_device, **motion_vector_support), + mesh_layouts: MeshLayouts::new(&render_device, **animated_mesh_motion_vectors), per_object_buffer_batch_size: GpuArrayBuffer::::batch_size(&render_device), binding_arrays_are_usable: binding_arrays_are_usable(&render_device), } @@ -1058,6 +1058,8 @@ bitflags::bitflags! { const READS_VIEW_TRANSMISSION_TEXTURE = 1 << 12; const LIGHTMAPPED = 1 << 13; const IRRADIANCE_VOLUME = 1 << 14; + const ANIMATED_SKIN_MOTION_VECTORS = 1 << 15; + const ANIMATED_MORPH_MOTION_VECTORS = 1 << 16; const LAST_FLAG = Self::IRRADIANCE_VOLUME.bits(); // Bitfields @@ -1175,14 +1177,16 @@ fn is_skinned(layout: &MeshVertexBufferLayoutRef) -> bool { layout.0.contains(Mesh::ATTRIBUTE_JOINT_INDEX) && layout.0.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT) } -pub fn setup_morph_and_skinning_defs( +pub fn setup_morph_and_skinning( mesh_layouts: &MeshLayouts, layout: &MeshVertexBufferLayoutRef, offset: u32, key: &MeshPipelineKey, shader_defs: &mut Vec, vertex_attributes: &mut Vec, -) -> BindGroupLayout { + skinning_motion_vector_support: bool, + morph_motion_vector_support: bool, +) -> (BindGroupLayout, bool) { let mut add_skin_data = || { shader_defs.push("SKINNED".into()); vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(offset)); @@ -1193,19 +1197,37 @@ pub fn setup_morph_and_skinning_defs( match (is_skinned(layout), is_morphed, is_lightmapped) { (true, false, _) => { add_skin_data(); - mesh_layouts.skinned.clone() + if skinning_motion_vector_support { + shader_defs.push("ANIMATED_MESH_MOTION_VECTORS".into()); + (mesh_layouts.skinned_motion_vectors.clone().unwrap(), true) + } else { + (mesh_layouts.skinned.clone(), false) + } } (true, true, _) => { add_skin_data(); shader_defs.push("MORPH_TARGETS".into()); - mesh_layouts.morphed_skinned.clone() + if skinning_motion_vector_support && morph_motion_vector_support { + shader_defs.push("ANIMATED_MESH_MOTION_VECTORS".into()); + ( + mesh_layouts.morphed_skinned_motion_vectors.clone().unwrap(), + true, + ) + } else { + (mesh_layouts.morphed_skinned.clone(), false) + } } (false, true, _) => { shader_defs.push("MORPH_TARGETS".into()); - mesh_layouts.morphed.clone() + if morph_motion_vector_support { + shader_defs.push("ANIMATED_MESH_MOTION_VECTORS".into()); + (mesh_layouts.morphed_motion_vectors.clone().unwrap(), true) + } else { + (mesh_layouts.morphed.clone(), false) + } } - (false, false, true) => mesh_layouts.lightmapped.clone(), - (false, false, false) => mesh_layouts.model_only.clone(), + (false, false, true) => (mesh_layouts.lightmapped.clone(), false), + (false, false, false) => (mesh_layouts.model_only.clone(), false), } } @@ -1265,14 +1287,19 @@ impl SpecializedMeshPipeline for MeshPipeline { shader_defs.push("MULTISAMPLED".into()); }; - bind_group_layout.push(setup_morph_and_skinning_defs( - &self.mesh_layouts, - layout, - 6, - &key, - &mut shader_defs, - &mut vertex_attributes, - )); + bind_group_layout.push( + setup_morph_and_skinning( + &self.mesh_layouts, + layout, + 6, + &key, + &mut shader_defs, + &mut vertex_attributes, + false, + false, + ) + .0, + ); if key.contains(MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION) { shader_defs.push("SCREEN_SPACE_AMBIENT_OCCLUSION".into()); @@ -1463,18 +1490,6 @@ impl SpecializedMeshPipeline for MeshPipeline { )); } - let mut push_constant_ranges = Vec::with_capacity(1); - if cfg!(all( - feature = "webgl", - target_arch = "wasm32", - not(feature = "webgpu") - )) { - push_constant_ranges.push(PushConstantRange { - stages: ShaderStages::VERTEX, - range: 0..4, - }); - } - Ok(RenderPipelineDescriptor { vertex: VertexState { shader: MESH_SHADER_HANDLE, @@ -1493,7 +1508,7 @@ impl SpecializedMeshPipeline for MeshPipeline { })], }), layout: bind_group_layout, - push_constant_ranges, + push_constant_ranges: vec![], primitive: PrimitiveState { front_face: FrontFace::Ccw, cull_mode: Some(Face::Back), @@ -1598,17 +1613,54 @@ pub fn prepare_mesh_bind_group( groups.model_only = Some(layouts.model_only(&render_device, &model)); let skin = skins_uniform.buffer.buffer(); - if let Some(skin) = skin { - groups.skinned = Some(layouts.skinned(&render_device, &model, skin)); + let previous_skin = skins_uniform + .previous_buffer + .as_ref() + .and_then(BufferVec::buffer); + match (skin, previous_skin) { + (Some(skin), None) => groups.skinned = Some(layouts.skinned(&render_device, &model, skin)), + (Some(skin), Some(previous_skin)) => { + groups.skinned = + Some(layouts.skinned_motion_vectors(&render_device, &model, skin, previous_skin)) + } + _ => {} } if let Some(weights) = weights_uniform.buffer.buffer() { + let previous_weights = weights_uniform + .previous_buffer + .as_ref() + .and_then(BufferVec::buffer); + for (id, gpu_mesh) in meshes.iter() { if let Some(targets) = gpu_mesh.morph_targets.as_ref() { - let group = if let Some(skin) = skin.filter(|_| is_skinned(&gpu_mesh.layout)) { - layouts.morphed_skinned(&render_device, &model, skin, weights, targets) - } else { - layouts.morphed(&render_device, &model, weights, targets) + let group = match ( + skin.filter(|_| is_skinned(&gpu_mesh.layout)), + previous_skin, + previous_weights, + ) { + (None, _, Some(previous_weights)) => layouts.morphed_motion_vectors( + &render_device, + &model, + weights, + targets, + previous_weights, + ), + + (Some(skin), Some(previous_skin), Some(previous_weights)) => layouts + .morphed_skinned_motion_vectors( + &render_device, + &model, + skin, + weights, + targets, + previous_skin, + previous_weights, + ), + (Some(skin), _, None) => { + layouts.morphed_skinned(&render_device, &model, skin, weights, targets) + } + _ => layouts.morphed(&render_device, &model, weights, targets), }; groups.morph_targets.insert(id, group); } @@ -1790,12 +1842,6 @@ impl RenderCommand

for DrawMesh { pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); let batch_range = item.batch_range(); - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] - pass.set_push_constants( - ShaderStages::VERTEX, - 0, - &(batch_range.start as i32).to_le_bytes(), - ); match &gpu_mesh.buffer_info { GpuBufferInfo::Indexed { buffer, diff --git a/crates/bevy_pbr/src/render/mod.rs b/crates/bevy_pbr/src/render/mod.rs index 53bc9bcde14b4..79eb0243dcf6a 100644 --- a/crates/bevy_pbr/src/render/mod.rs +++ b/crates/bevy_pbr/src/render/mod.rs @@ -13,4 +13,5 @@ pub use light::*; pub use mesh::*; pub use mesh_bindings::MeshLayouts; pub use mesh_view_bindings::*; +pub use morph::MorphUniform; pub use skin::{extract_skins, prepare_skins, SkinIndex, SkinUniform, MAX_JOINTS}; diff --git a/crates/bevy_pbr/src/render/morph.wgsl b/crates/bevy_pbr/src/render/morph.wgsl index bd6eb1d041832..c7f1defaf2b2e 100644 --- a/crates/bevy_pbr/src/render/morph.wgsl +++ b/crates/bevy_pbr/src/render/morph.wgsl @@ -6,6 +6,9 @@ @group(1) @binding(2) var morph_weights: MorphWeights; @group(1) @binding(3) var morph_targets: texture_3d; +#ifdef ANIMATED_MESH_MOTION_VECTORS +@group(1) @binding(7) var previous_morph_weights: MorphWeights; +#endif // NOTE: Those are the "hardcoded" values found in `MorphAttributes` struct // in crates/bevy_render/src/mesh/morph/visitors.rs diff --git a/crates/bevy_pbr/src/render/skinning.wgsl b/crates/bevy_pbr/src/render/skinning.wgsl index 4307a22ddffa0..b1374bef1f388 100644 --- a/crates/bevy_pbr/src/render/skinning.wgsl +++ b/crates/bevy_pbr/src/render/skinning.wgsl @@ -5,6 +5,9 @@ #ifdef SKINNED @group(1) @binding(1) var joint_matrices: SkinnedMesh; +#ifdef ANIMATED_MESH_MOTION_VECTORS +@group(1) @binding(6) var previous_joint_matrices: SkinnedMesh; +#endif fn skin_model( indexes: vec4, diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 7a26b46972ad4..74060064ec7cd 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -699,12 +699,6 @@ impl RenderCommand

for DrawMesh2d { pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); let batch_range = item.batch_range(); - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] - pass.set_push_constants( - ShaderStages::VERTEX, - 0, - &(batch_range.start as i32).to_le_bytes(), - ); match &gpu_mesh.buffer_info { GpuBufferInfo::Indexed { buffer, From 78e7e97f7d6a8d806e82c7802c931af4e6cc557b Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Tue, 23 Apr 2024 11:33:05 -0700 Subject: [PATCH 3/7] Gate pipeline keys behind motion vector prepass --- crates/bevy_pbr/src/prepass/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index dbc724309ef73..6b2f958641213 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -835,10 +835,10 @@ pub fn queue_prepass_material_meshes( mesh_key |= MeshPipelineKey::LIGHTMAPPED; } - if skins.previous_buffer.is_some() { + if skins.previous_buffer.is_some() && motion_vector_prepass.is_some() { mesh_key |= MeshPipelineKey::ANIMATED_SKIN_MOTION_VECTORS; } - if morphs.previous_buffer.is_some() { + if morphs.previous_buffer.is_some() && motion_vector_prepass.is_some() { mesh_key |= MeshPipelineKey::ANIMATED_MORPH_MOTION_VECTORS; } From fb45148e993b5245ebe1f06099a5a13b2d541822 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Tue, 23 Apr 2024 11:43:52 -0700 Subject: [PATCH 4/7] WIP --- crates/bevy_pbr/src/render/mesh.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index d48a7d1f5cbc7..a9e2f2ce8a64e 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1646,7 +1646,6 @@ pub fn prepare_mesh_bind_group( targets, previous_weights, ), - (Some(skin), Some(previous_skin), Some(previous_weights)) => layouts .morphed_skinned_motion_vectors( &render_device, @@ -1751,6 +1750,11 @@ impl RenderCommand

for SetMeshBindGroup { }; let skin_index = skin_indices.indices.get(entity); let morph_index = morph_indices.indices.get(entity); + // TODO: Previous indices may be none if this entity was created this frame, + // but there are other valid previous frame animated meshes, so we still + // have to set the dynamic offset for these + let previous_skin_index = skin_indices.previous_indices.get(entity); + let previous_morph_index = morph_indices.previous_indices.get(entity); let is_skinned = skin_index.is_some(); let is_morphed = morph_index.is_some(); From d559532a1f491aea84087b6a9058363cb8924495 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Tue, 23 Apr 2024 11:45:51 -0700 Subject: [PATCH 5/7] WIP --- crates/bevy_pbr/src/render/mesh.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index a9e2f2ce8a64e..d5acf22605398 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1752,7 +1752,8 @@ impl RenderCommand

for SetMeshBindGroup { let morph_index = morph_indices.indices.get(entity); // TODO: Previous indices may be none if this entity was created this frame, // but there are other valid previous frame animated meshes, so we still - // have to set the dynamic offset for these + // have to set the dynamic offset since we're using the motion vector version + // of the bind group let previous_skin_index = skin_indices.previous_indices.get(entity); let previous_morph_index = morph_indices.previous_indices.get(entity); From f9a35409236b37c7086c4f4c58d57069d3a19fe3 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Tue, 23 Apr 2024 11:52:12 -0700 Subject: [PATCH 6/7] WIP --- crates/bevy_pbr/src/prepass/prepass.wgsl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 89e98fb3139ff..6bb8ff46af40e 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -96,10 +96,14 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #ifdef MOTION_VECTOR_PREPASS // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. // See https://github.com/gfx-rs/naga/issues/2416 +#ifdef ANIMATED_MESH_MOTION_VECTORS + // TODO: Account for previous skin, morph, or skin+morph +#else out.previous_world_position = mesh_functions::mesh_position_local_to_world( mesh_functions::get_previous_model_matrix(vertex_no_morph.instance_index), vec4(vertex.position, 1.0) ); +#endif // ANIMATED_MESH_MOTION_VECTORS #endif // MOTION_VECTOR_PREPASS #ifdef VERTEX_OUTPUT_INSTANCE_INDEX @@ -136,6 +140,9 @@ fn fragment(in: VertexOutput) -> FragmentOutput { // range -2,2, so this needs to be scaled by 0.5. And the V direction goes // down where clip space y goes up, so y needs to be flipped. out.motion_vector = (clip_position - previous_clip_position) * vec2(0.5, -0.5); +#ifdef ANIMATED_MESH_MOTION_VECTORS + out.motion_vector *= prepass_bindings::motion_vectors_mask; +#endif // ANIMATED_MESH_MOTION_VECTORS #endif // MOTION_VECTOR_PREPASS #ifdef DEFERRED_PREPASS From 3334d0bd5ff643f54a605672a70d05b6b271b455 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Tue, 23 Apr 2024 11:54:33 -0700 Subject: [PATCH 7/7] WIP --- crates/bevy_core_pipeline/src/taa/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/bevy_core_pipeline/src/taa/mod.rs b/crates/bevy_core_pipeline/src/taa/mod.rs index 3bcd8652e4b54..1a0425eba4bd5 100644 --- a/crates/bevy_core_pipeline/src/taa/mod.rs +++ b/crates/bevy_core_pipeline/src/taa/mod.rs @@ -124,8 +124,6 @@ pub struct TemporalAntiAliasBundle { /// /// [Currently](https://github.com/bevyengine/bevy/issues/8423) cannot be used with [`bevy_render::camera::OrthographicProjection`]. /// -/// Currently does not support skinned meshes and morph targets. -/// There will probably be ghosting artifacts if used with them. /// Does not work well with alpha-blended meshes as it requires depth writing to determine motion. /// /// It is very important that correct motion vectors are written for everything on screen.