How to properly manage motion vectors for primitives in render scene

We have built a system where we re-use skeletal and static mesh components for characters, by pooling them individually on a global actor and attaching them to different actors on demand in order to avoid having to pay the initialization cost for these components. When they are not in use they are marked as invisible. This is used for character clothing as well as items.

We’ve been facing some issues with motion vectors in this regard, and were wondering if what we’re seeing is working as expected or we’re using it incorrectly.

* When making a pooled mesh visible again some frames after hiding it for re-use, we find that the previous transform is still picked up and used as the previous transform for motion vector calculations. From looking at the render code it appears like these are only cleaned up if they are at least 10 frames old, but this seems a little odd, as using a 10 frame old transform as a previous transform will generate a wrong velocity value since the delta time is used from a single frame only?

-> We’ve been able to work around this one by calling ResetSceneVelocity() just before setting the visibility to false for the object, this ensures the velocity is instantly removed from the velocity data buffer.

* When calling ResetSceneVelocity in the same frame we update the transform of the same primitive, it appears like the previous transform is not correctly cleared from the primitive. The call to ResetSceneVelocity does clean up the velocity data, however the position update is later received during the render scene update (FScene::Update), where it re-initializes the velocity data using the old previous velocity.

> PrimitiveSceneInfo->bRegisteredWithVelocityData = true;

> VelocityData.UpdateTransform(PrimitiveSceneInfo, LocalToWorld, PrimitiveSceneProxy->GetLocalToWorld());

-> We’re able to work around this by using ResetSceneVelocity along with MarkRenderStateDirty, as the proxy re-creation path uses the LocalToWorld as the default value for the previous transform.

> VelocityData.UpdateTransform(PrimitiveSceneInfo, PrimitiveTransforms[PrimitiveIndex], PrimitiveTransforms[PrimitiveIndex]);

As the code documentation for ResetSceneVelocity indicates that it’s for use when teleporting objects, it feels strange that it can’t be used for this. How is this supposed to be used?

* When creating a mesh component, and moving it in the same frame a scene velocity is immediately generated from this movement delta. We’re spawning/constructing an item from code, and then attaching it to a character socket. Since the movement is in a single frame, I was expecting this to be coalesced on the render side, as it is on other frames besides the one where the meshes are spawned. However as RegisterComponent immediately registers a primitive scene proxy with the initial transform, and further moves call MarkRenderTransformDirty it ends up the case that a velocity is generated as 2 transforms are set for this proxy in a single frame. Is there a way to delay creation of the render proxy until we’ve attached the item to the character?

-> We were able to work around this by calling MarkRenderStateDirty() after the final transform is set, which appears to avoid the bad initial transform from ending up in scene velocity as the proxy is destroyed before it reaches the scene add.

* Calling ClearMotionVectors does not work when it’s called before another update to the mesh (for example before the animation blueprint ticks) because motion vectors are only cleared when EBoneTransformUpdateMethod::ClearMotionVector is the last update in the list.

-> We work around this by calling the function after the skeletal meshes have finished ticking

All of the above feel quite hard to work with in the way that we’re using the API’s, which indicates that we’re likely using them wrong. Would you be able to advise how to correctly manage the scene velocity for these cases?

[Attachment Removed]

Hello,

Apologies for the delay. It looks like your pooling system might be similar to FNiagaraRendererComponents::PostSystemTick_GameThread and FNiagaraRendererGeometryCache::PostSystemTick_GameThread which have a pool of components and do something like this:

UGeometryCacheComponent* GeometryComponent = CreateOrGetPooledComponent(Properties, AttachComponent, Emitter, DefaultCacheIndex, ParticleIndex, ParticleID, PoolIndex);
...
// activate the component
if (!GeometryComponent->IsActive())
{
     GeometryComponent->ResetSceneVelocity();
     GeometryComponent->SetVisibility(true, true);
     GeometryComponent->Activate(false);
 }

And when deactivating it does this:

Component->Deactivate();
Component->SetVisibility(false, true);

Calling ResetSceneVelocity() should remove the velocity data if it has bRegisteredWithVelocityData=true. It might be useful to review that code in Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererComponents.cpp

As for the motion issues related to attaching components to character skinned meshes, there may be some nuance about how ClearMotionVectors should be called, I’ve reached out to my colleagues more familiar with the system for guidance.

[Attachment Removed]

Hi Alex,

Thank you for the reply!

The Niagara renderer components look really similar to what we are doing, so I’m not sure how it works there :slight_smile:

They call ResetSceneVelocity just after SetVisibility(true) - however, at least in our case the SetVisibility call does not immediately re-create the scene proxy, it is only done at the end of the frame when UPrimitiveComponent::CreateRenderState_Concurrent is called. And when there is no scene proxy the call to ResetSceneVelocity is a no-op.

There is also still the issue of calling ResetSceneVelocity in the same frame as teleporting the object. It appears Niagara works around it by having the object invisible, which means that there is no proxy and so no delta move but instead a new proxy is created without a move command. However should there not be a way to reset scene velocity for an object that is just moved, without re-creating a new render state?

I’ll try and set up a test using the niagara system to see if the behavior is different from my expectations here, but the code looks remarably similar to what we’re doing.

[Attachment Removed]

Hello,

Glad to hear the Niagara example looks similar, hopefully that will help shed some light on what’s going on.

We’re spawning/constructing an item from code, and then attaching it to a character socket. Since the movement is in a single frame, I was expecting this to be coalesced on the render side, as it is on other frames besides the one where the meshes are spawned. However as RegisterComponent immediately registers a primitive scene proxy with the initial transform, and further moves call MarkRenderTransformDirty it ends up the case that a velocity is generated as 2 transforms are set for this proxy in a single frame.

Can you provide details/psuedocode showing how you are attaching objects from your pool?

[Attachment Removed]

Thanks for providing those details, it does appear that these are all existing issues though I haven’t confirmed if we have a correct way of doing this that doesn’t involve calling ClearMotionVector(), ResetSceneVelocity() or some combination.

I’m working with the team to determine how best to log these issues.

There is also the possibility of hiding an attached mesh on creation and then showing it on the next frame to delay render proxy creation, but this is also unintuitive.

[Attachment Removed]

In this particular case those actors are not (yet) pooled. So it’s the case of spawning a new actor and moving it in the same frame that is giving the large motion vectors.

Our code is structured so that spawning item + equipping it is handled in 2 distinctive parts of the code, so the spawn spawns in a dummy position before immediately being moved to the equip socket in the second piece of code.

Spawning:

ASvItemActor* SpawnedActor = InWorld.SpawnActor<ASvItemActor>(ItemDefinition->GetActorType(), InSpawnLocation, InSpawnRotation);

Equipping:

const FAttachmentTransformRules AttachmentRules(

EAttachmentRule::SnapToTarget,

EAttachmentRule::SnapToTarget,

EAttachmentRule::KeepWorld,

false);

InItemActor->AttachToComponent(CharacterRepresentation->GetAnimationMesh(), AttachmentRules, EquippableData.EquipmentSlot);

if (EquippableData.HasAttachmentOffset)

{

InItemActor->SetActorRelativeTransform(EquippableData.AttachmentOffset, false, nullptr, ETeleportType::TeleportPhysics);

}

NEW (hotfix which kind of works around the issue by forcing to create a new render proxy at the end of the frame, which skirts around the issue by never issuing a ‘move’ command to the renderer

void ASvItemActor::ResetMotionVectors()

{

ForEachComponent<UPrimitiveComponent>(false,[](UPrimitiveComponent* InPrimitive)

{

// NOTE: Instead of using ResetSceneVelocity here we have to clear the entire render proxy

// It appears like, resetting the scene velocity does not work if you also move the actor in the same frame, as the move just places the scene

// velocity right back where it was (since it only updates the current frame transform, and leaves the previous frame transform which is wrong

// already and the reason we want to reset the motion vectors in the first place)

InPrimitive->MarkRenderStateDirty();

});

}

To clarify, the issue for us us because move on the render side is implemented like this:

(NOTE: Always passing PrimitiveSceneProxy->GetLocalToWorld() and LocalToWorld (i.e. the currently set transform on the proxy from creation, and the new one we want to move to). This completely disregards any request to clear motion vectors etc)

FScene::Update line ~5912

if (ShouldPrimitiveOutputVelocity(PrimitiveSceneInfo->Proxy, GetShaderPlatform()))

{

PrimitiveSceneInfo->bRegisteredWithVelocityData = true;

VelocityData.UpdateTransform(PrimitiveSceneInfo, LocalToWorld, PrimitiveSceneProxy->GetLocalToWorld());

}

[Attachment Removed]