Add MeshDeformation utility for generic meshes vertices morphing (eg. via Blend Shapes or Cloth)

This commit is contained in:
Wojtek Figat
2023-07-03 09:49:23 +02:00
parent 60181a29a3
commit 99ee0b1bfe
26 changed files with 600 additions and 368 deletions

View File

@@ -12,6 +12,7 @@
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Level/SceneObjectsFactory.h"
#include "Engine/Serialization/Serialization.h"
@@ -34,6 +35,12 @@ AnimatedModel::AnimatedModel(const SpawnParams& params)
AnimationGraph.Loaded.Bind<AnimatedModel, &AnimatedModel::OnGraphLoaded>(this);
}
AnimatedModel::~AnimatedModel()
{
if (_deformation)
Delete(_deformation);
}
void AnimatedModel::ResetAnimation()
{
GraphInstance.ClearState();
@@ -56,11 +63,6 @@ void AnimatedModel::UpdateAnimation()
// Request an animation update
Animations::AddToUpdate(this);
}
else
{
// Allow to use blend shapes without animation graph assigned
_blendShapes.Update(SkinnedModel.Get());
}
}
void AnimatedModel::SetupSkinningData()
@@ -292,7 +294,7 @@ void AnimatedModel::SetParameterValue(const Guid& id, const Variant& value)
float AnimatedModel::GetBlendShapeWeight(const StringView& name)
{
for (auto& e : _blendShapes.Weights)
for (auto& e : _blendShapeWeights)
{
if (e.First == name)
return e.Second;
@@ -302,36 +304,130 @@ float AnimatedModel::GetBlendShapeWeight(const StringView& name)
void AnimatedModel::SetBlendShapeWeight(const StringView& name, float value)
{
const auto* model = SkinnedModel.Get();
CHECK(model);
model->WaitForLoaded();
value = Math::Clamp(value, -1.0f, 1.0f);
for (int32 i = 0; i < _blendShapes.Weights.Count(); i++)
const bool isZero = Math::IsZero(value);
if (!_deformation && !isZero)
_deformation = New<MeshDeformation>();
Function<void(const MeshBase*, MeshDeformationData&)> deformer;
deformer.Bind<AnimatedModel, &AnimatedModel::RunBlendShapeDeformer>(this);
for (int32 i = 0; i < _blendShapeWeights.Count(); i++)
{
auto& e = _blendShapes.Weights[i];
auto& e = _blendShapeWeights[i];
if (e.First == name)
{
if (Math::IsZero(value))
if (isZero)
{
_blendShapes.WeightsDirty = true;
_blendShapes.Weights.RemoveAt(i);
_blendShapeWeights.RemoveAt(i);
// Remove deformers for meshes using this blend shape
for (const auto& lod : model->LODs)
{
for (const auto& mesh : lod.Meshes)
{
for (const auto& blendShape : mesh.BlendShapes)
{
if (blendShape.Name == name)
{
for (int32 j = 0; j < _blendShapeMeshes.Count(); j++)
{
auto& blendShapeMesh = _blendShapeMeshes[j];
if (blendShapeMesh.LODIndex == mesh.GetLODIndex() && blendShapeMesh.MeshIndex == mesh.GetIndex())
{
blendShapeMesh.Usages--;
if (blendShapeMesh.Usages == 0)
{
_deformation->RemoveDeformer(blendShapeMesh.LODIndex, blendShapeMesh.MeshIndex, MeshBufferType::Vertex0, deformer);
_blendShapeMeshes.RemoveAt(j);
}
break;
}
}
break;
}
}
}
}
}
else if (Math::NotNearEqual(e.Second, value))
{
_blendShapes.WeightsDirty = true;
// Update blend shape weight
e.Second = value;
// Dirty deformers for meshes using this blend shape
for (const auto& lod : model->LODs)
{
for (const auto& mesh : lod.Meshes)
{
for (const auto& blendShape : mesh.BlendShapes)
{
if (blendShape.Name == name)
{
_deformation->Dirty(mesh.GetLODIndex(), mesh.GetIndex(), MeshBufferType::Vertex0);
break;
}
}
}
}
}
return;
}
}
if (!isZero)
{
auto& e = _blendShapes.Weights.AddOne();
// Add blend shape weight
auto& e = _blendShapeWeights.AddOne();
e.First = name;
e.Second = value;
_blendShapes.WeightsDirty = true;
// Add deformers for meshes using this blend shape
for (const auto& lod : model->LODs)
{
for (const auto& mesh : lod.Meshes)
{
for (const auto& blendShape : mesh.BlendShapes)
{
if (blendShape.Name == name)
{
int32 i = 0;
for (; i < _blendShapeMeshes.Count(); i++)
{
auto& blendShapeMesh = _blendShapeMeshes[i];
if (blendShapeMesh.LODIndex == mesh.GetLODIndex() && blendShapeMesh.MeshIndex == mesh.GetIndex())
{
blendShapeMesh.Usages++;
break;
}
}
if (i == _blendShapeMeshes.Count())
{
auto& blendShapeMesh = _blendShapeMeshes.AddOne();
blendShapeMesh.LODIndex = mesh.GetLODIndex();
blendShapeMesh.MeshIndex = mesh.GetIndex();
blendShapeMesh.Usages = 1;
_deformation->AddDeformer(blendShapeMesh.LODIndex, blendShapeMesh.MeshIndex, MeshBufferType::Vertex0, deformer);
}
break;
}
}
}
}
}
}
void AnimatedModel::ClearBlendShapeWeights()
{
_blendShapes.Clear();
if (_deformation)
{
Function<void(const MeshBase*, MeshDeformationData&)> deformer;
deformer.Bind<AnimatedModel, &AnimatedModel::RunBlendShapeDeformer>(this);
for (auto e : _blendShapeMeshes)
_deformation->RemoveDeformer(e.LODIndex, e.MeshIndex, MeshBufferType::Vertex0, deformer);
}
_blendShapeWeights.Clear();
_blendShapeMeshes.Clear();
}
void AnimatedModel::PlaySlotAnimation(const StringView& slotName, Animation* anim, float speed, float blendInTime, float blendOutTime, int32 loopCount)
@@ -469,6 +565,87 @@ void AnimatedModel::SyncParameters()
}
}
void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationData& deformation)
{
PROFILE_CPU_NAMED("BlendShapes");
auto* skinnedMesh = (const SkinnedMesh*)mesh;
// Estimate the range of the vertices to modify by the currently active blend shapes
uint32 minVertexIndex = MAX_uint32, maxVertexIndex = 0;
bool useNormals = false;
Array<Pair<const BlendShape&, const float>, InlinedAllocation<32>> blendShapes;
for (const BlendShape& blendShape : skinnedMesh->BlendShapes)
{
for (auto& q : _blendShapeWeights)
{
if (q.First == blendShape.Name)
{
const float weight = q.Second * blendShape.Weight;
blendShapes.Add(Pair<const BlendShape&, const float>(blendShape, weight));
minVertexIndex = Math::Min(minVertexIndex, blendShape.MinVertexIndex);
maxVertexIndex = Math::Max(maxVertexIndex, blendShape.MaxVertexIndex);
useNormals |= blendShape.UseNormals;
break;
}
}
}
// Blend all blend shapes
auto vertexCount = (uint32)mesh->GetVertexCount();
auto data = (VB0SkinnedElementType*)deformation.VertexBuffer.Data.Get();
for (const auto& q : blendShapes)
{
// TODO: use SIMD
if (useNormals)
{
for (int32 i = 0; i < q.First.Vertices.Count(); i++)
{
const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i];
ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount);
VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex);
vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second;
Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta;
vertex.Normal = normal * 0.5f + 0.5f;
}
}
else
{
for (int32 i = 0; i < q.First.Vertices.Count(); i++)
{
const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i];
ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount);
VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex);
vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second;
}
}
}
if (useNormals)
{
// Normalize normal vectors and rebuild tangent frames (tangent frame is in range [-1;1] but packed to [0;1] range)
// TODO: use SIMD
for (uint32 vertexIndex = minVertexIndex; vertexIndex <= maxVertexIndex; vertexIndex++)
{
VB0SkinnedElementType& vertex = *(data + vertexIndex);
Float3 normal = vertex.Normal.ToFloat3() * 2.0f - 1.0f;
normal.Normalize();
vertex.Normal = normal * 0.5f + 0.5f;
Float3 tangent = vertex.Tangent.ToFloat3() * 2.0f - 1.0f;
tangent = tangent - ((tangent | normal) * normal);
tangent.Normalize();
const auto tangentSign = vertex.Tangent.W;
vertex.Tangent = tangent * 0.5f + 0.5f;
vertex.Tangent.W = tangentSign;
}
}
// Mark as dirty to be cleared before next rendering
deformation.DirtyMinIndex = Math::Min(minVertexIndex, deformation.DirtyMinIndex);
deformation.DirtyMaxIndex = Math::Max(maxVertexIndex, deformation.DirtyMaxIndex);
}
void AnimatedModel::BeginPlay(SceneBeginData* data)
{
if (SkinnedModel && SkinnedModel->IsLoaded())
@@ -513,31 +690,26 @@ void AnimatedModel::OnActiveInTreeChanged()
void AnimatedModel::UpdateBounds()
{
auto model = SkinnedModel.Get();
if (CustomBounds.GetSize().LengthSquared() > 0.01f)
{
BoundingBox::Transform(CustomBounds, _transform, _box);
}
else if (SkinnedModel && SkinnedModel->IsLoaded())
else if (model && model->IsLoaded())
{
BoundingBox box = model->LODs[0].GetBox(_transform, _deformation);
if (GraphInstance.NodesPose.Count() != 0)
{
// Per-bone bounds estimated from positions
auto& skeleton = SkinnedModel->Skeleton;
auto& skeleton = model->Skeleton;
const int32 bonesCount = skeleton.Bones.Count();
#define GET_NODE_POS(i) _transform.LocalToWorld(GraphInstance.NodesPose[skeleton.Bones[i].NodeIndex].GetTranslation())
BoundingBox box(GET_NODE_POS(0));
for (int32 boneIndex = 1; boneIndex < bonesCount; boneIndex++)
box.Merge(GET_NODE_POS(boneIndex));
_box = box;
#undef GET_NODE_POS
}
else
{
_box = SkinnedModel->GetBox(_transform.GetWorld());
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
box.Merge(_transform.LocalToWorld(GraphInstance.NodesPose[skeleton.Bones.Get()[boneIndex].NodeIndex].GetTranslation()));
}
_box = box;
// Apply margin based on model dimensions
const Vector3 modelBoxSize = SkinnedModel->GetBox().GetSize();
const Vector3 modelBoxSize = model->GetBox().GetSize();
const Vector3 center = _box.GetCenter();
const Vector3 sizeHalf = Vector3::Max(_box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f;
_box = BoundingBox(center - sizeHalf, center + sizeHalf);
@@ -595,7 +767,6 @@ void AnimatedModel::OnAnimationUpdated_Async()
}
UpdateBounds();
_blendShapes.Update(SkinnedModel.Get());
}
void AnimatedModel::OnAnimationUpdated_Sync()
@@ -616,23 +787,20 @@ void AnimatedModel::OnAnimationUpdated()
void AnimatedModel::OnSkinnedModelChanged()
{
Entries.Release();
if (SkinnedModel && !SkinnedModel->IsLoaded())
{
UpdateBounds();
GraphInstance.Invalidate();
}
if (_deformation)
_deformation->Clear();
GraphInstance.NodesSkeleton = SkinnedModel;
}
void AnimatedModel::OnSkinnedModelLoaded()
{
Entries.SetupIfInvalid(SkinnedModel);
GraphInstance.Invalidate();
if (_blendShapes.Weights.HasItems())
_blendShapes.WeightsDirty = true;
PreInitSkinningData();
}
@@ -699,7 +867,7 @@ void AnimatedModel::Draw(RenderContext& renderContext)
if (!SkinnedModel || !SkinnedModel->IsLoaded())
return;
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Animated Model rendering to Global SDF
return;
if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
return; // No supported
Matrix world;
@@ -721,9 +889,9 @@ void AnimatedModel::Draw(RenderContext& renderContext)
SkinnedMesh::DrawInfo draw;
draw.Buffer = &Entries;
draw.Skinning = &_skinningData;
draw.BlendShapes = &_blendShapes;
draw.World = &world;
draw.DrawState = &_drawState;
draw.Deformation = _deformation;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
draw.DrawModes = DrawModes & renderContext.View.GetShadowsDrawPassMask(ShadowsMode);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
@@ -764,9 +932,9 @@ void AnimatedModel::Draw(RenderContextBatch& renderContextBatch)
SkinnedMesh::DrawInfo draw;
draw.Buffer = &Entries;
draw.Skinning = &_skinningData;
draw.BlendShapes = &_blendShapes;
draw.World = &world;
draw.DrawState = &_drawState;
draw.Deformation = _deformation;
draw.DrawModes = DrawModes;
draw.Bounds = _sphere;
draw.Bounds.Center -= renderContext.View.Origin;
@@ -957,6 +1125,13 @@ bool AnimatedModel::GetMeshData(const MeshReference& mesh, MeshBufferType type,
return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count);
}
MeshDeformation* AnimatedModel::GetMeshDeformation() const
{
if (!_deformation)
_deformation = New<MeshDeformation>();
return _deformation;
}
void AnimatedModel::OnDeleteObject()
{
// Ensure this object is no longer referenced for anim update
@@ -965,14 +1140,6 @@ void AnimatedModel::OnDeleteObject()
ModelInstanceActor::OnDeleteObject();
}
void AnimatedModel::OnTransformChanged()
{
// Base
ModelInstanceActor::OnTransformChanged();
UpdateBounds();
}
void AnimatedModel::WaitForModelLoad()
{
if (SkinnedModel)