Skip to content

Commit

Permalink
Implement motion vectors and TAA for skinned meshes and meshes with
Browse files Browse the repository at this point in the history
morph targets.

This is a revamped version of bevyengine#9902. Instead of adding more bind group
layouts as that patch did, which created a combinatorial explosion of
layouts, this patch unconditionally adds `prev_joint_matrices` and
`prev_morph_weights` bindings to the shader bind groups. These add no
significant overhead if unused because we simply bind dummy buffers, and
the driver strips them out if unused. We already do this extensively
with the various `StandardMaterial` bindings as well as the mesh view
bindings, so this approach isn't anything new.

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 bevyengine#8423.
  • Loading branch information
pcwalton committed May 30, 2024
1 parent 4065098 commit 83d376c
Show file tree
Hide file tree
Showing 11 changed files with 436 additions and 78 deletions.
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::HAVE_PREVIOUS_SKIN) {
shader_defs.push("HAVE_PREVIOUS_SKIN".into());
}

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

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

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

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

let pipeline_id = pipelines.specialize(
&pipeline_cache,
&material_pipeline,
Expand Down
22 changes: 22 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::HAVE_PREVIOUS_SKIN) {
shader_defs.push("HAVE_PREVIOUS_SKIN".into());
}

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

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

// If the previous frame have skins or morph targets, note that.
if mesh_instance
.flags
.contains(RenderMeshInstanceFlags::HAVE_PREVIOUS_SKIN)
{
mesh_key |= MeshPipelineKey::HAVE_PREVIOUS_SKIN;
}
if mesh_instance
.flags
.contains(RenderMeshInstanceFlags::HAVE_PREVIOUS_MORPH)
{
mesh_key |= MeshPipelineKey::HAVE_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. 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 HAVE_PREVIOUS_MORPH
let prev_vertex = morph_prev_vertex(vertex_no_morph);
#else // HAVE_PREVIOUS_MORPH
let prev_vertex = vertex_no_morph;
#endif // HAVE_PREVIOUS_MORPH

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

// Take skinning into account.
#ifdef SKINNED

#ifdef HAVE_PREVIOUS_SKIN
let prev_model = skinning::skin_prev_model(
prev_vertex.joint_indices,
prev_vertex.joint_weights,
);
#else // HAVE_PREVIOUS_SKIN
let prev_model = mesh_functions::get_previous_model_matrix(prev_vertex.instance_index);
#endif // HAVE_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 83d376c

Please sign in to comment.