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
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Eliminate the regression
pcwalton committed May 31, 2024
commit 02e384c2e0cc9048cf5c5d33dcc7b1f5909fa207
26 changes: 14 additions & 12 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
@@ -701,18 +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::HAS_PREVIOUS_SKIN)
{
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
}
if mesh_instance
.flags
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
{
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
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(
26 changes: 14 additions & 12 deletions crates/bevy_pbr/src/prepass/mod.rs
Original file line number Diff line number Diff line change
@@ -861,18 +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::HAS_PREVIOUS_SKIN)
{
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
}
if mesh_instance
.flags
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
{
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
// 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(
173 changes: 127 additions & 46 deletions crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
@@ -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::MotionVectorPrepass,
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::entity::EntityHashMap;
@@ -1407,8 +1408,8 @@ bitflags::bitflags! {
const IRRADIANCE_VOLUME = 1 << 14;
const VISIBILITY_RANGE_DITHER = 1 << 15;
const SCREEN_SPACE_REFLECTIONS = 1 << 16;
const HAS_PREVIOUS_SKIN = 1 << 17;
const HAS_PREVIOUS_MORPH = 1 << 18;
const HAS_PREVIOUS_SKIN = 1 << 17;
const HAS_PREVIOUS_MORPH = 1 << 18;
const LAST_FLAG = Self::HAS_PREVIOUS_MORPH.bits();

// Bitfields
@@ -1550,22 +1551,41 @@ pub fn setup_morph_and_skinning_defs(
};
let is_morphed = key.intersects(MeshPipelineKey::MORPH_TARGETS);
let is_lightmapped = key.intersects(MeshPipelineKey::LIGHTMAPPED);
match (is_skinned(layout), is_morphed, is_lightmapped) {
(true, false, _) => {
let motion_vector_prepass = key.intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS);
match (
is_skinned(layout),
is_morphed,
is_lightmapped,
motion_vector_prepass,
) {
(true, false, _, true) => {
add_skin_data();
mesh_layouts.skinned_motion.clone()
}
(true, false, _, false) => {
add_skin_data();
mesh_layouts.skinned.clone()
}
(true, true, _) => {
(true, true, _, true) => {
add_skin_data();
shader_defs.push("MORPH_TARGETS".into());
mesh_layouts.morphed_skinned_motion.clone()
}
(true, true, _, false) => {
add_skin_data();
shader_defs.push("MORPH_TARGETS".into());
mesh_layouts.morphed_skinned.clone()
}
(false, true, _) => {
(false, true, _, true) => {
shader_defs.push("MORPH_TARGETS".into());
mesh_layouts.morphed_motion.clone()
}
(false, true, _, false) => {
shader_defs.push("MORPH_TARGETS".into());
mesh_layouts.morphed.clone()
}
(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, _) => mesh_layouts.model_only.clone(),
}
}

@@ -1906,10 +1926,16 @@ impl SpecializedMeshPipeline for MeshPipeline {
#[derive(Resource, Default)]
pub struct MeshBindGroups {
model_only: Option<BindGroup>,
skinned: Option<BindGroup>,
morph_targets: HashMap<AssetId<Mesh>, BindGroup>,
skinned: Option<MeshBindGroupPair>,
morph_targets: HashMap<AssetId<Mesh>, MeshBindGroupPair>,
lightmaps: HashMap<AssetId<Image>, BindGroup>,
}

pub struct MeshBindGroupPair {
motion_vectors: BindGroup,
no_motion_vectors: BindGroup,
}

impl MeshBindGroups {
pub fn reset(&mut self) {
self.model_only = None;
@@ -1925,16 +1951,33 @@ impl MeshBindGroups {
lightmap: Option<AssetId<Image>>,
is_skinned: bool,
morph: bool,
motion_vectors: bool,
) -> Option<&BindGroup> {
match (is_skinned, morph, lightmap) {
(_, true, _) => self.morph_targets.get(&asset_id),
(true, false, _) => self.skinned.as_ref(),
(_, true, _) => self
.morph_targets
.get(&asset_id)
.map(|bind_group_pair| bind_group_pair.get(motion_vectors)),
(true, false, _) => self
.skinned
.as_ref()
.map(|bind_group_pair| bind_group_pair.get(motion_vectors)),
(false, false, Some(lightmap)) => self.lightmaps.get(&lightmap),
(false, false, None) => self.model_only.as_ref(),
}
}
}

impl MeshBindGroupPair {
fn get(&self, motion_vectors: bool) -> &BindGroup {
if motion_vectors {
&self.motion_vectors
} else {
&self.no_motion_vectors
}
}
}

#[allow(clippy::too_many_arguments)]
pub fn prepare_mesh_bind_group(
meshes: Res<RenderAssets<GpuMesh>>,
@@ -1953,6 +1996,7 @@ pub fn prepare_mesh_bind_group(
render_lightmaps: Res<RenderLightmaps>,
) {
groups.reset();

let layouts = &mesh_pipeline.mesh_layouts;

let model = if let Some(cpu_batched_instance_buffer) = cpu_batched_instance_buffer {
@@ -1976,7 +2020,10 @@ pub fn prepare_mesh_bind_group(
let skin = skins_uniform.current_buffer.buffer();
if let Some(skin) = skin {
let prev_skin = skins_uniform.prev_buffer.buffer().unwrap_or(skin);
Copy link
Contributor

Choose a reason for hiding this comment

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

We can bind the same buffer twice? I would've thought you needed a dummy buffer. Maybe that rule is only for storage resources, and does not apply to uniform buffers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't think there was any kind of rule against binding a read-only buffer twice. I can see problems with read-write buffers, but read-only seems always fine. It's extremely common for textures, so I don't see why it wouldn't be for buffers...

groups.skinned = Some(layouts.skinned(&render_device, &model, skin, prev_skin));
groups.skinned = Some(MeshBindGroupPair {
motion_vectors: layouts.skinned_motion(&render_device, &model, skin, prev_skin),
no_motion_vectors: layouts.skinned(&render_device, &model, skin),
});
}

// Create the morphed bind groups just like we did for the skinned bind
@@ -1985,21 +2032,45 @@ pub fn prepare_mesh_bind_group(
let prev_weights = weights_uniform.prev_buffer.buffer().unwrap_or(weights);
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)) {
let prev_skin = skins_uniform.prev_buffer.buffer().unwrap_or(skin);
layouts.morphed_skinned(
&render_device,
&model,
skin,
weights,
targets,
prev_skin,
prev_weights,
)
} else {
layouts.morphed(&render_device, &model, weights, targets, prev_weights)
let bind_group_pair = match skin.filter(|_| is_skinned(&gpu_mesh.layout)) {
Some(skin) => {
let prev_skin = skins_uniform.prev_buffer.buffer().unwrap_or(skin);
MeshBindGroupPair {
motion_vectors: layouts.morphed_skinned_motion(
&render_device,
&model,
skin,
weights,
targets,
prev_skin,
prev_weights,
),
no_motion_vectors: layouts.morphed_skinned(
&render_device,
&model,
skin,
weights,
targets,
),
}
}
None => MeshBindGroupPair {
motion_vectors: layouts.morphed_motion(
&render_device,
&model,
weights,
targets,
prev_weights,
),
no_motion_vectors: layouts.morphed(
&render_device,
&model,
weights,
targets,
),
},
};
groups.morph_targets.insert(id, group);
groups.morph_targets.insert(id, bind_group_pair);
}
}
}
@@ -2063,13 +2134,13 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
SRes<MorphIndices>,
SRes<RenderLightmaps>,
);
type ViewQuery = ();
type ViewQuery = Has<MotionVectorPrepass>;
type ItemQuery = ();

#[inline]
fn render<'w>(
item: &P,
_view: (),
has_motion_vector_prepass: bool,
_item_query: Option<()>,
(bind_groups, mesh_instances, skin_indices, morph_indices, lightmaps): SystemParamItem<
'w,
@@ -2101,8 +2172,13 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
.get(entity)
.map(|render_lightmap| render_lightmap.image);

let Some(bind_group) = bind_groups.get(mesh_asset_id, lightmap, is_skinned, is_morphed)
else {
let Some(bind_group) = bind_groups.get(
mesh_asset_id,
lightmap,
is_skinned,
is_morphed,
has_motion_vector_prepass,
) else {
error!(
"The MeshBindGroups resource wasn't set in the render phase. \
It should be set by the prepare_mesh_bind_group system.\n\
@@ -2126,24 +2202,29 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
offset_count += 1;
}

// Attach the previous skin index for motion vector computation. If
// there isn't one, just use zero as the shader will ignore it.
if current_skin_index.is_some() {
match prev_skin_index {
Some(prev_skin_index) => dynamic_offsets[offset_count] = prev_skin_index.index,
None => dynamic_offsets[offset_count] = 0,
// Attach motion vectors if needed.
if has_motion_vector_prepass {
// Attach the previous skin index for motion vector computation. If
// there isn't one, just use zero as the shader will ignore it.
if current_skin_index.is_some() {
match prev_skin_index {
Some(prev_skin_index) => dynamic_offsets[offset_count] = prev_skin_index.index,
None => dynamic_offsets[offset_count] = 0,
}
offset_count += 1;
}
offset_count += 1;
}

// Attach the previous morph index for motion vector computation. If
// there isn't one, just use zero as the shader will ignore it.
if current_morph_index.is_some() {
match prev_morph_index {
Some(prev_morph_index) => dynamic_offsets[offset_count] = prev_morph_index.index,
None => dynamic_offsets[offset_count] = 0,
// Attach the previous morph index for motion vector computation. If
// there isn't one, just use zero as the shader will ignore it.
if current_morph_index.is_some() {
match prev_morph_index {
Some(prev_morph_index) => {
dynamic_offsets[offset_count] = prev_morph_index.index;
}
None => dynamic_offsets[offset_count] = 0,
}
offset_count += 1;
}
offset_count += 1;
}

pass.set_bind_group(I, bind_group, &dynamic_offsets[0..offset_count]);
158 changes: 146 additions & 12 deletions crates/bevy_pbr/src/render/mesh_bindings.rs
Original file line number Diff line number Diff line change
@@ -106,16 +106,29 @@ pub struct MeshLayouts {
/// Also includes the uniform for skinning
pub skinned: BindGroupLayout,

/// Like [`MeshLayouts::skinned`], but includes slots for the previous
/// frame's joint matrices, so that we can compute motion vectors.
pub skinned_motion: BindGroupLayout,

/// Also includes the uniform and [`MorphAttributes`] for morph targets.
///
/// [`MorphAttributes`]: bevy_render::mesh::morph::MorphAttributes
pub morphed: BindGroupLayout,

/// Like [`MeshLayouts::morphed`], but includes a slot for the previous
/// frame's morph weights, so that we can compute motion vectors.
pub morphed_motion: BindGroupLayout,

/// Also includes both uniforms for skinning and morph targets, also the
/// morph target [`MorphAttributes`] binding.
///
/// [`MorphAttributes`]: bevy_render::mesh::morph::MorphAttributes
pub morphed_skinned: BindGroupLayout,

/// Like [`MeshLayouts::morphed_skinned`], but includes slots for the
/// previous frame's joint matrices and morph weights, so that we can
/// compute motion vectors.
pub morphed_skinned_motion: BindGroupLayout,
}

impl MeshLayouts {
@@ -127,8 +140,11 @@ impl MeshLayouts {
model_only: Self::model_only_layout(render_device),
lightmapped: Self::lightmapped_layout(render_device),
skinned: Self::skinned_layout(render_device),
skinned_motion: Self::skinned_motion_layout(render_device),
morphed: Self::morphed_layout(render_device),
morphed_motion: Self::morphed_motion_layout(render_device),
morphed_skinned: Self::morphed_skinned_layout(render_device),
morphed_skinned_motion: Self::morphed_skinned_motion_layout(render_device),
}
}

@@ -148,6 +164,22 @@ impl MeshLayouts {
fn skinned_layout(render_device: &RenderDevice) -> BindGroupLayout {
render_device.create_bind_group_layout(
"skinned_mesh_layout",
&BindGroupLayoutEntries::with_indices(
ShaderStages::VERTEX,
(
(0, layout_entry::model(render_device)),
// The current frame's joint matrix buffer.
(1, layout_entry::skinning()),
),
),
)
}

/// Creates the layout for skinned meshes with the infrastructure to compute
/// motion vectors.
fn skinned_motion_layout(render_device: &RenderDevice) -> BindGroupLayout {
render_device.create_bind_group_layout(
"skinned_motion_mesh_layout",
&BindGroupLayoutEntries::with_indices(
ShaderStages::VERTEX,
(
@@ -163,6 +195,23 @@ impl MeshLayouts {

/// Creates the layout for meshes with morph targets.
fn morphed_layout(render_device: &RenderDevice) -> BindGroupLayout {
render_device.create_bind_group_layout(
"morphed_mesh_layout",
&BindGroupLayoutEntries::with_indices(
ShaderStages::VERTEX,
(
(0, layout_entry::model(render_device)),
// The current frame's morph weight buffer.
(2, layout_entry::weights()),
(3, layout_entry::targets()),
),
),
)
}

/// Creates the layout for meshes with morph targets and the infrastructure
/// to compute motion vectors.
fn morphed_motion_layout(render_device: &RenderDevice) -> BindGroupLayout {
render_device.create_bind_group_layout(
"morphed_mesh_layout",
&BindGroupLayoutEntries::with_indices(
@@ -202,6 +251,29 @@ impl MeshLayouts {
)
}

/// Creates the bind group layout for meshes with both skins and morph
/// targets, in addition to the infrastructure to compute motion vectors.
fn morphed_skinned_motion_layout(render_device: &RenderDevice) -> BindGroupLayout {
render_device.create_bind_group_layout(
"morphed_skinned_motion_mesh_layout",
&BindGroupLayoutEntries::with_indices(
ShaderStages::VERTEX,
(
(0, layout_entry::model(render_device)),
// The current frame's joint matrix buffer.
(1, layout_entry::skinning()),
// The current frame's morph weight buffer.
(2, layout_entry::weights()),
(3, layout_entry::targets()),
// The previous frame's joint matrix buffer.
(6, layout_entry::skinning()),
// The previous frame's morph weight buffer.
(7, layout_entry::weights()),
),
),
)
}

fn lightmapped_layout(render_device: &RenderDevice) -> BindGroupLayout {
render_device.create_bind_group_layout(
"lightmapped_mesh_layout",
@@ -244,21 +316,39 @@ impl MeshLayouts {
}

/// Creates the bind group for skinned meshes with no morph targets.
pub fn skinned(
&self,
render_device: &RenderDevice,
model: &BindingResource,
current_skin: &Buffer,
) -> BindGroup {
render_device.create_bind_group(
"skinned_mesh_bind_group",
&self.skinned,
&[
entry::model(0, model.clone()),
entry::skinning(1, current_skin),
],
)
}

/// Creates the bind group for skinned meshes with no morph targets, with
/// the infrastructure to compute motion vectors.
///
/// `current_skin` is the buffer of joint matrices for this frame;
/// `prev_skin` is the buffer for the previous frame. The latter is used for
/// motion vector computation. If there is no such applicable buffer,
/// `current_skin` and `prev_skin` will reference the same buffer.
pub fn skinned(
pub fn skinned_motion(
&self,
render_device: &RenderDevice,
model: &BindingResource,
current_skin: &Buffer,
prev_skin: &Buffer,
) -> BindGroup {
render_device.create_bind_group(
"skinned_mesh_bind_group",
&self.skinned,
"skinned_motion_mesh_bind_group",
&self.skinned_motion,
&[
entry::model(0, model.clone()),
entry::skinning(1, current_skin),
@@ -268,12 +358,32 @@ impl MeshLayouts {
}

/// Creates the bind group for meshes with no skins but morph targets.
pub fn morphed(
&self,
render_device: &RenderDevice,
model: &BindingResource,
current_weights: &Buffer,
targets: &TextureView,
) -> BindGroup {
render_device.create_bind_group(
"morphed_mesh_bind_group",
&self.morphed,
&[
entry::model(0, model.clone()),
entry::weights(2, current_weights),
entry::targets(3, targets),
],
)
}

/// Creates the bind group for meshes with no skins but morph targets, in
/// addition to the infrastructure to compute motion vectors.
///
/// `current_weights` is the buffer of morph weights for this frame;
/// `prev_weights` is the buffer for the previous frame. The latter is used
/// for motion vector computation. If there is no such applicable buffer,
/// `current_weights` and `prev_weights` will reference the same buffer.
pub fn morphed(
pub fn morphed_motion(
&self,
render_device: &RenderDevice,
model: &BindingResource,
@@ -282,8 +392,8 @@ impl MeshLayouts {
prev_weights: &Buffer,
) -> BindGroup {
render_device.create_bind_group(
"morphed_mesh_bind_group",
&self.morphed,
"morphed_motion_mesh_bind_group",
&self.morphed_motion,
&[
entry::model(0, model.clone()),
entry::weights(2, current_weights),
@@ -294,10 +404,6 @@ impl MeshLayouts {
}

/// Creates the bind group for meshes with skins and morph targets.
///
/// See the documentation for [`skinned`] and [`morphed`] above for more
/// information about the `current_skin`, `prev_skin`, `current_weights`,
/// and `prev_weights` buffers.
#[allow(clippy::too_many_arguments)]
pub fn morphed_skinned(
&self,
@@ -306,12 +412,40 @@ impl MeshLayouts {
current_skin: &Buffer,
current_weights: &Buffer,
targets: &TextureView,
prev_skin: &Buffer,
prev_weights: &Buffer,
) -> BindGroup {
render_device.create_bind_group(
"morphed_skinned_mesh_bind_group",
&self.morphed_skinned,
&[
entry::model(0, model.clone()),
entry::skinning(1, current_skin),
entry::weights(2, current_weights),
entry::targets(3, targets),
],
)
}

/// Creates the bind group for meshes with skins and morph targets, in
/// addition to the infrastructure to compute motion vectors.
///
/// See the documentation for [`MeshLayouts::skinned_motion`] and
/// [`MeshLayouts::morphed_motion`] above for more information about the
/// `current_skin`, `prev_skin`, `current_weights`, and `prev_weights`
/// buffers.
#[allow(clippy::too_many_arguments)]
pub fn morphed_skinned_motion(
&self,
render_device: &RenderDevice,
model: &BindingResource,
current_skin: &Buffer,
current_weights: &Buffer,
targets: &TextureView,
prev_skin: &Buffer,
prev_weights: &Buffer,
) -> BindGroup {
render_device.create_bind_group(
"morphed_skinned_motion_mesh_bind_group",
&self.morphed_skinned_motion,
&[
entry::model(0, model.clone()),
entry::skinning(1, current_skin),
16 changes: 6 additions & 10 deletions examples/animation/animated_fox.rs
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@ use std::time::Duration;

use bevy::{
animation::{animate_targets, RepeatAnimation},
core_pipeline::experimental::taa::{TemporalAntiAliasBundle, TemporalAntiAliasPlugin},
pbr::CascadeShadowConfigBuilder,
prelude::*,
};
@@ -16,8 +15,7 @@ fn main() {
color: Color::WHITE,
brightness: 2000.,
})
.insert_resource(Msaa::Off)
.add_plugins((DefaultPlugins, TemporalAntiAliasPlugin))
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, setup_scene_once_loaded.before(animate_targets))
.add_systems(Update, keyboard_animation_control)
@@ -62,13 +60,11 @@ fn setup(
});

// Camera
commands
.spawn(Camera3dBundle {
transform: Transform::from_xyz(100.0, 100.0, 150.0)
.looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),
..default()
})
.insert(TemporalAntiAliasBundle::default());
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(100.0, 100.0, 150.0)
.looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),
..default()
});

// Plane
commands.spawn(PbrBundle {