Skip to content

Commit

Permalink
Implement motion vectors and TAA for skinned meshes and meshes with m…
Browse files Browse the repository at this point in the history
…orph targets. (#13572)

This is a revamped equivalent to #9902, though it shares none of the
code. It handles all special cases that I've tested correctly.

The overall technique consists of double-buffering the joint matrix and
morph weights buffers, as most of the previous attempts to solve this
problem did. The process is generally straightforward. Note that, to
avoid regressing the ability of mesh extraction, skin extraction, and
morph target extraction to run in parallel, I had to add a new system to
rendering, `set_mesh_motion_vector_flags`. The comment there explains
the details; it generally runs very quickly.

I've tested this with modified versions of the `animated_fox`,
`morph_targets`, and `many_foxes` examples that add TAA, and the patch
works. To avoid bloating those examples, I didn't add switches for TAA
to them.

Addresses points (1) and (2) of #8423.

## Changelog

### Fixed

* Motion vectors, and therefore TAA, are now supported for meshes with
skins and/or morph targets.
  • Loading branch information
pcwalton authored May 31, 2024
1 parent 42d8037 commit be053b1
Show file tree
Hide file tree
Showing 12 changed files with 677 additions and 101 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 @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions crates/bevy_pbr/src/deferred/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,14 @@ impl SpecializedRenderPipeline for DeferredLightingLayout {
shader_defs.push("SCREEN_SPACE_REFLECTIONS".into());
}

if key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) {
shader_defs.push("HAS_PREVIOUS_SKIN".into());
}

if key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) {
shader_defs.push("HAS_PREVIOUS_MORPH".into());
}

// Always true, since we're in the deferred lighting pipeline
shader_defs.push("DEFERRED_PREPASS".into());

Expand Down
16 changes: 16 additions & 0 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,22 @@ pub fn queue_material_meshes<M: Material>(
mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
}

if motion_vector_prepass {
// If the previous frame have skins or morph targets, note that.
if mesh_instance
.flags
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
{
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
}
if mesh_instance
.flags
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
{
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
}
}

let pipeline_id = pipelines.specialize(
&pipeline_cache,
&material_pipeline,
Expand Down
24 changes: 24 additions & 0 deletions crates/bevy_pbr/src/prepass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,14 @@ where
shader_defs.push("MOTION_VECTOR_PREPASS".into());
}

if key.mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) {
shader_defs.push("HAS_PREVIOUS_SKIN".into());
}

if key.mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) {
shader_defs.push("HAS_PREVIOUS_MORPH".into());
}

if key.mesh_key.intersects(
MeshPipelineKey::NORMAL_PREPASS
| MeshPipelineKey::MOTION_VECTOR_PREPASS
Expand Down Expand Up @@ -853,6 +861,22 @@ pub fn queue_prepass_material_meshes<M: Material>(
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
}

// If the previous frame has skins or morph targets, note that.
if motion_vector_prepass.is_some() {
if mesh_instance
.flags
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
{
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
}
if mesh_instance
.flags
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
{
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
}
}

let pipeline_id = pipelines.specialize(
&pipeline_cache,
&prepass_pipeline,
Expand Down
59 changes: 54 additions & 5 deletions crates/bevy_pbr/src/prepass/prepass.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,26 @@ fn morph_vertex(vertex_in: Vertex) -> Vertex {
}
return vertex;
}
#endif

// Returns the morphed position of the given vertex from the previous frame.
//
// This function is used for motion vector calculation, and, as such, it doesn't
// bother morphing the normals and tangents.
fn morph_prev_vertex(vertex_in: Vertex) -> Vertex {
var vertex = vertex_in;
let weight_count = morph::layer_count();
for (var i: u32 = 0u; i < weight_count; i ++) {
let weight = morph::prev_weight_at(i);
if weight == 0.0 {
continue;
}
vertex.position += weight * morph::morph(vertex.index, morph::position_offset, i);
// Don't bother morphing normals and tangents; we don't need them for
// motion vector calculation.
}
return vertex;
}
#endif // MORPH_TARGETS

@vertex
fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
Expand Down Expand Up @@ -93,12 +112,42 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
out.color = vertex.color;
#endif

// Compute the motion vector for TAA among other purposes. For this we need
// to know where the vertex was last frame.
#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

// Take morph targets into account.
#ifdef MORPH_TARGETS

#ifdef HAS_PREVIOUS_MORPH
let prev_vertex = morph_prev_vertex(vertex_no_morph);
#else // HAS_PREVIOUS_MORPH
let prev_vertex = vertex_no_morph;
#endif // HAS_PREVIOUS_MORPH

#else // MORPH_TARGETS
let prev_vertex = vertex_no_morph;
#endif // MORPH_TARGETS

// Take skinning into account.
#ifdef SKINNED

#ifdef HAS_PREVIOUS_SKIN
let prev_model = skinning::skin_prev_model(
prev_vertex.joint_indices,
prev_vertex.joint_weights,
);
#else // HAS_PREVIOUS_SKIN
let prev_model = mesh_functions::get_previous_model_matrix(prev_vertex.instance_index);
#endif // HAS_PREVIOUS_SKIN

#else // SKINNED
let prev_model = mesh_functions::get_previous_model_matrix(prev_vertex.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)
prev_model,
vec4<f32>(prev_vertex.position, 1.0)
);
#endif // MOTION_VECTOR_PREPASS

Expand Down
Loading

0 comments on commit be053b1

Please sign in to comment.