Skip to content

Commit

Permalink
Add motion vectors support for animated meshes
Browse files Browse the repository at this point in the history
Co-authored-by: Robert Swain <[email protected]>
  • Loading branch information
nicopap and superdump committed Oct 23, 2023
1 parent d938275 commit 5f713d5
Show file tree
Hide file tree
Showing 13 changed files with 575 additions and 320 deletions.
2 changes: 0 additions & 2 deletions crates/bevy_core_pipeline/src/taa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,6 @@ pub struct TemporalAntiAliasBundle {
///
/// 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.
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ type DrawMaterial<M> = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetMaterialBindGroup<M, 1>,
SetMeshBindGroup<2>,
SetMeshBindGroup<2, false>,
DrawMesh,
);

Expand Down
21 changes: 17 additions & 4 deletions crates/bevy_pbr/src/prepass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ use bevy_render::{
use bevy_transform::prelude::GlobalTransform;
use bevy_utils::tracing::error;

use crate::render::{MorphIndices, SkinIndices};
use crate::{
prepare_materials, setup_morph_and_skinning_defs, AlphaMode, DrawMesh, Material,
MaterialPipeline, MaterialPipelineKey, MeshLayouts, MeshPipeline, MeshPipelineKey,
Expand Down Expand Up @@ -370,7 +371,7 @@ where
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut bind_group_layouts = vec![if key
.mesh_key
.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS)
.contains(MeshPipelineKey::VIEW_MOTION_VECTOR_PREPASS)
{
self.view_layout_motion_vectors.clone()
} else {
Expand Down Expand Up @@ -479,7 +480,7 @@ where
shader_defs.push("PREPASS_FRAGMENT".into());
}

let bind_group = setup_morph_and_skinning_defs(
let bind_group = setup_morph_and_skinning_defs::<true>(
&self.mesh_layouts,
layout,
4,
Expand All @@ -491,6 +492,8 @@ where

let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;

// Note that if you are updating this code, you probably need to update the equivalent
// code that depends on color target states in crates/bevy_core_pipeline/src/prepass/node.rs
// Setup prepass fragment targets - normals in slot 0 (or None if not needed), motion vectors in slot 1
let mut targets = vec![
key.mesh_key
Expand All @@ -502,7 +505,7 @@ where
write_mask: ColorWrites::ALL,
}),
key.mesh_key
.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS)
.contains(MeshPipelineKey::VIEW_MOTION_VECTOR_PREPASS)
.then_some(ColorTargetState {
format: MOTION_VECTOR_PREPASS_FORMAT,
// BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases.
Expand Down Expand Up @@ -747,6 +750,8 @@ pub fn queue_prepass_material_meshes<M: Material>(
render_mesh_instances: Res<RenderMeshInstances>,
render_materials: Res<RenderMaterials<M>>,
render_material_instances: Res<RenderMaterialInstances<M>>,
morph_indices: Res<MorphIndices>,
skin_indices: Res<SkinIndices>,
mut views: Query<
(
&ExtractedView,
Expand Down Expand Up @@ -807,6 +812,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
view_key |= MeshPipelineKey::NORMAL_PREPASS;
}
if motion_vector_prepass.is_some() {
view_key |= MeshPipelineKey::VIEW_MOTION_VECTOR_PREPASS;
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
}

Expand All @@ -831,6 +837,13 @@ pub fn queue_prepass_material_meshes<M: Material>(

let mut mesh_key =
MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) | view_key;

let missing_motion_vector = morph_indices.missing_previous(visible_entity)
|| skin_indices.missing_previous(visible_entity);

if missing_motion_vector {
mesh_key &= !MeshPipelineKey::MOTION_VECTOR_PREPASS;
}
if mesh.morph_targets.is_some() {
mesh_key |= MeshPipelineKey::MORPH_TARGETS;
}
Expand Down Expand Up @@ -983,7 +996,7 @@ pub type DrawPrepass<M> = (
SetItemPipeline,
SetPrepassViewBindGroup<0>,
SetMaterialBindGroup<M, 1>,
SetMeshBindGroup<2>,
SetMeshBindGroup<2, true>,
DrawMesh,
);

Expand Down
31 changes: 28 additions & 3 deletions crates/bevy_pbr/src/prepass/prepass.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,29 @@ fn morph_vertex(vertex_in: Vertex) -> Vertex {
}
return vertex;
}


#ifdef MOTION_VECTOR_PREPASS
fn morph_previous_position(vertex: Vertex) -> vec3<f32> {
var vertex_position = vertex.position;
let weight_count = bevy_pbr::morph::layer_count();
for (var i: u32 = 0u; i < weight_count; i++) {
let weight = bevy_pbr::morph::previous_weight_at(i);
if weight != 0.0 {
vertex_position += weight * bevy_pbr::morph::morph(vertex.index, bevy_pbr::morph::position_offset, i);
}
}
return vertex_position;
}
#endif // MOTION_VECTOR_PREPASS
#endif

@vertex
fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
var out: VertexOutput;

#ifdef MORPH_TARGETS
var vertex = morph::morph_vertex(vertex_no_morph);
var vertex = morph_vertex(vertex_no_morph);
#else
var vertex = vertex_no_morph;
#endif
Expand Down Expand Up @@ -94,11 +109,21 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
#endif // MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS

#ifdef MOTION_VECTOR_PREPASS
#ifdef MORPH_TARGETS
var previous_position = morph_previous_position(vertex_no_morph);
#else
var previous_position = vertex.position;
#endif // MORPH_TARGETS
#ifdef SKINNED
var previous_model = skinning::previous_skin_model(vertex.joint_indices, vertex.joint_weights);
#else
// 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
var previous_model = mesh_functions::get_previous_model_matrix(vertex_no_morph.instance_index);
#endif // SKINNED
out.previous_world_position = mesh_functions::mesh_position_local_to_world(
mesh_functions::get_previous_model_matrix(vertex_no_morph.instance_index),
vec4<f32>(vertex.position, 1.0)
previous_model,
vec4<f32>(previous_position, 1.0)
);
#endif // MOTION_VECTOR_PREPASS

Expand Down
62 changes: 62 additions & 0 deletions crates/bevy_pbr/src/render/double_buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use std::mem;

use bevy_ecs::prelude::Entity;
use bevy_render::render_resource::{BufferUsages, BufferVec};
use bevy_utils::EntityHashMap;
use bytemuck::Pod;

/// A double buffer of `T`.
///
/// Use [`DoubleBuffer::swap`] to swap buffer,
/// access the current buffer with [`DoubleBuffer::current`],
/// and the previous one with [`DoubleBuffer::previous`].
#[derive(Default)]
pub struct DoubleBuffer<T> {
pub previous: T,
pub current: T,
}

impl<T> DoubleBuffer<T> {
pub fn swap(&mut self, swap_buffer: bool) {
if swap_buffer {
mem::swap(&mut self.current, &mut self.previous);
}
}
}

pub type DoubleBufferVec<T> = DoubleBuffer<BufferVec<T>>;

impl<T: Pod> DoubleBufferVec<T> {
pub const fn new(buffer_usage: BufferUsages) -> Self {
DoubleBufferVec {
previous: BufferVec::new(buffer_usage),
current: BufferVec::new(buffer_usage),
}
}

pub fn clear(&mut self, swap_buffer: bool) {
self.swap(swap_buffer);
self.current.clear();
}
}

pub type DoubleEntityMap<T> = DoubleBuffer<EntityHashMap<Entity, T>>;

impl<T> DoubleEntityMap<T> {
pub fn clear(&mut self, swap_buffer: bool) {
self.swap(swap_buffer);
self.current.clear();
}

pub fn insert(&mut self, entity: Entity, value: T) {
self.current.insert(entity, value);
}

pub fn missing_previous(&self, entity: &Entity) -> bool {
let current = self.current.contains_key(entity);
let previous = self.previous.contains_key(entity);
// Either it's already missing (therefore there is no "previous" to miss)
// or it's not missing and there is no "previous", so we miss previous.
current && !previous
}
}
Loading

0 comments on commit 5f713d5

Please sign in to comment.