Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement motion vectors and TAA for skinned meshes and meshes with morph targets. #13572

Merged
merged 4 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the vertex_no_morph.instance_index workaround no longer valid?

Copy link
Contributor Author

@pcwalton pcwalton May 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tried animated_fox with TAA enabled on the Direct3D 12 backend and it works fine. I guess whatever the issue was is now fixed. Or my changes to the shaders perturbed the bug out of existence.

// 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